mirror of https://github.com/astral-sh/ruff
lazy/eager bounds/etc should not cause assignability failures
This commit is contained in:
parent
b1e354bd99
commit
e4be76e812
|
|
@ -2484,7 +2484,7 @@ impl<'db> Type<'db> {
|
|||
disjointness_visitor,
|
||||
),
|
||||
|
||||
(Type::KnownInstance(left), right) => left.instance_fallback(db).has_relation_to_impl(
|
||||
(Type::KnownInstance(left), right) => left.has_relation_to_impl(
|
||||
db,
|
||||
right,
|
||||
inferable,
|
||||
|
|
@ -2628,6 +2628,10 @@ impl<'db> Type<'db> {
|
|||
first.is_equivalent_to_impl(db, second, inferable, visitor)
|
||||
}
|
||||
|
||||
(Type::KnownInstance(first), Type::KnownInstance(second)) => {
|
||||
ConstraintSet::from(first.is_equivalent_to(db, second))
|
||||
}
|
||||
|
||||
(Type::Union(first), Type::Union(second)) => {
|
||||
first.is_equivalent_to_impl(db, second, inferable, visitor)
|
||||
}
|
||||
|
|
@ -8121,6 +8125,78 @@ impl<'db> KnownInstanceType<'db> {
|
|||
self.class(db).is_subclass_of(db, class)
|
||||
}
|
||||
|
||||
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::TypeVar(self_typevar), Self::TypeVar(other_typevar)) => {
|
||||
self_typevar.is_same_typevar_as(db, other_typevar)
|
||||
}
|
||||
(
|
||||
Self::SubscriptedProtocol(_)
|
||||
| Self::SubscriptedGeneric(_)
|
||||
| Self::TypeVar(_)
|
||||
| Self::TypeAliasType(_)
|
||||
| Self::Deprecated(_)
|
||||
| Self::Field(_)
|
||||
| Self::ConstraintSet(_)
|
||||
| Self::UnionType(_)
|
||||
| Self::Literal(_)
|
||||
| Self::Annotated(_)
|
||||
| Self::TypeGenericAlias(_)
|
||||
| Self::NewType(_),
|
||||
Self::SubscriptedProtocol(_)
|
||||
| Self::SubscriptedGeneric(_)
|
||||
| Self::TypeVar(_)
|
||||
| Self::TypeAliasType(_)
|
||||
| Self::Deprecated(_)
|
||||
| Self::Field(_)
|
||||
| Self::ConstraintSet(_)
|
||||
| Self::UnionType(_)
|
||||
| Self::Literal(_)
|
||||
| Self::Annotated(_)
|
||||
| Self::TypeGenericAlias(_)
|
||||
| Self::NewType(_),
|
||||
) => self == other,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_relation_to_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
target: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self, target) {
|
||||
(Self::TypeVar(self_typevar), Type::KnownInstance(Self::TypeVar(other_typevar))) => {
|
||||
ConstraintSet::from(self_typevar.is_same_typevar_as(db, other_typevar))
|
||||
}
|
||||
(
|
||||
Self::SubscriptedProtocol(_)
|
||||
| Self::SubscriptedGeneric(_)
|
||||
| Self::TypeVar(_)
|
||||
| Self::TypeAliasType(_)
|
||||
| Self::Deprecated(_)
|
||||
| Self::Field(_)
|
||||
| Self::ConstraintSet(_)
|
||||
| Self::UnionType(_)
|
||||
| Self::Literal(_)
|
||||
| Self::Annotated(_)
|
||||
| Self::TypeGenericAlias(_)
|
||||
| Self::NewType(_),
|
||||
_,
|
||||
) => self.instance_fallback(db).has_relation_to_impl(
|
||||
db,
|
||||
target,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the repr of the symbol at runtime
|
||||
fn repr(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||
struct KnownInstanceRepr<'db> {
|
||||
|
|
@ -8730,6 +8806,12 @@ impl<'db> TypeVarInstance<'db> {
|
|||
BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context))
|
||||
}
|
||||
|
||||
/// Returns whether two typevars represent the same logical typevar, regardless of e.g.
|
||||
/// differences in their bounds or constraints due to materialization.
|
||||
pub(crate) fn is_same_typevar_as(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.identity(db) == other.identity(db)
|
||||
}
|
||||
|
||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
|
||||
self.identity(db).name(db)
|
||||
}
|
||||
|
|
@ -12541,14 +12623,39 @@ static_assertions::assert_eq_size!(Type, [u8; 16]);
|
|||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::db::tests::{TestDbBuilder, setup_db};
|
||||
use crate::place::{typing_extensions_symbol, typing_symbol};
|
||||
use crate::db::tests::{TestDb, TestDbBuilder, setup_db};
|
||||
use crate::place::{ConsideredDefinitions, symbol, typing_extensions_symbol, typing_symbol};
|
||||
use crate::semantic_index::FileScopeId;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::DbWithWritableSystem as _;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use test_case::test_case;
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn get_symbol<'db>(
|
||||
db: &'db TestDb,
|
||||
file_name: &str,
|
||||
scopes: &[&str],
|
||||
symbol_name: &str,
|
||||
) -> Place<'db> {
|
||||
let file = system_path_to_file(db, file_name).expect("file to exist");
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let index = semantic_index(db, file);
|
||||
let mut file_scope_id = FileScopeId::global();
|
||||
let mut scope = file_scope_id.to_scope_id(db, file);
|
||||
for expected_scope_name in scopes {
|
||||
file_scope_id = index
|
||||
.child_scopes(file_scope_id)
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("scope of {expected_scope_name}"))
|
||||
.0;
|
||||
scope = file_scope_id.to_scope_id(db, file);
|
||||
assert_eq!(scope.name(db, &module), *expected_scope_name);
|
||||
}
|
||||
|
||||
symbol(db, scope, symbol_name, ConsideredDefinitions::EndOfScope).place
|
||||
}
|
||||
|
||||
/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
|
||||
/// the fallback to `typing_extensions` is working correctly.
|
||||
/// See [`KnownClass::canonical_module`] for more information.
|
||||
|
|
@ -12699,6 +12806,40 @@ pub(crate) mod tests {
|
|||
assert_eq!(intersection.display(&db).to_string(), "Never");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lazy_eager_typevar_equivalence() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.py",
|
||||
r#"
|
||||
def f[T = int](): ...
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let lazy_ty = get_symbol(&db, "/src/a.py", &["f"], "T").expect_type();
|
||||
let Type::KnownInstance(KnownInstanceType::TypeVar(lazy_typevar)) = lazy_ty else {
|
||||
panic!("unexpected type {}", lazy_ty.display(&db));
|
||||
};
|
||||
assert_eq!(
|
||||
lazy_typevar._default(&db),
|
||||
Some(TypeVarDefaultEvaluation::Lazy)
|
||||
);
|
||||
|
||||
let eager_ty = lazy_ty.normalized(&db);
|
||||
let Type::KnownInstance(KnownInstanceType::TypeVar(eager_typevar)) = eager_ty else {
|
||||
panic!("unexpected type {}", eager_ty.display(&db));
|
||||
};
|
||||
assert!(matches!(
|
||||
eager_typevar._default(&db),
|
||||
Some(TypeVarDefaultEvaluation::Eager(_))
|
||||
));
|
||||
|
||||
assert!(lazy_ty.is_equivalent_to(&db, eager_ty));
|
||||
assert!(lazy_ty.is_assignable_to(&db, eager_ty));
|
||||
assert!(eager_ty.is_assignable_to(&db, lazy_ty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_alias_variance() {
|
||||
use crate::db::tests::TestDb;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use super::builder::TypeInferenceBuilder;
|
||||
use crate::db::tests::{TestDb, setup_db};
|
||||
use crate::place::symbol;
|
||||
use crate::place::{ConsideredDefinitions, Place, global_symbol};
|
||||
use crate::place::global_symbol;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::scope::FileScopeId;
|
||||
use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map};
|
||||
use crate::types::tests::get_symbol;
|
||||
use crate::types::{KnownClass, KnownInstanceType, check_types};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{File, system_path_to_file};
|
||||
|
|
@ -13,31 +12,6 @@ use ruff_db::testing::{assert_function_query_was_not_run, assert_function_query_
|
|||
|
||||
use super::*;
|
||||
|
||||
#[track_caller]
|
||||
fn get_symbol<'db>(
|
||||
db: &'db TestDb,
|
||||
file_name: &str,
|
||||
scopes: &[&str],
|
||||
symbol_name: &str,
|
||||
) -> Place<'db> {
|
||||
let file = system_path_to_file(db, file_name).expect("file to exist");
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let index = semantic_index(db, file);
|
||||
let mut file_scope_id = FileScopeId::global();
|
||||
let mut scope = file_scope_id.to_scope_id(db, file);
|
||||
for expected_scope_name in scopes {
|
||||
file_scope_id = index
|
||||
.child_scopes(file_scope_id)
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("scope of {expected_scope_name}"))
|
||||
.0;
|
||||
scope = file_scope_id.to_scope_id(db, file);
|
||||
assert_eq!(scope.name(db, &module), *expected_scope_name);
|
||||
}
|
||||
|
||||
symbol(db, scope, symbol_name, ConsideredDefinitions::EndOfScope).place
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_diagnostic_messages(diagnostics: &[Diagnostic], expected: &[&str]) {
|
||||
let messages: Vec<&str> = diagnostics
|
||||
|
|
|
|||
Loading…
Reference in New Issue