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,
|
disjointness_visitor,
|
||||||
),
|
),
|
||||||
|
|
||||||
(Type::KnownInstance(left), right) => left.instance_fallback(db).has_relation_to_impl(
|
(Type::KnownInstance(left), right) => left.has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
right,
|
right,
|
||||||
inferable,
|
inferable,
|
||||||
|
|
@ -2628,6 +2628,10 @@ impl<'db> Type<'db> {
|
||||||
first.is_equivalent_to_impl(db, second, inferable, visitor)
|
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)) => {
|
(Type::Union(first), Type::Union(second)) => {
|
||||||
first.is_equivalent_to_impl(db, second, inferable, visitor)
|
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)
|
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
|
/// Return the repr of the symbol at runtime
|
||||||
fn repr(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
fn repr(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||||
struct KnownInstanceRepr<'db> {
|
struct KnownInstanceRepr<'db> {
|
||||||
|
|
@ -8730,6 +8806,12 @@ impl<'db> TypeVarInstance<'db> {
|
||||||
BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context))
|
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 {
|
pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
|
||||||
self.identity(db).name(db)
|
self.identity(db).name(db)
|
||||||
}
|
}
|
||||||
|
|
@ -12541,14 +12623,39 @@ static_assertions::assert_eq_size!(Type, [u8; 16]);
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db::tests::{TestDbBuilder, setup_db};
|
use crate::db::tests::{TestDb, TestDbBuilder, setup_db};
|
||||||
use crate::place::{typing_extensions_symbol, typing_symbol};
|
use crate::place::{ConsideredDefinitions, symbol, typing_extensions_symbol, typing_symbol};
|
||||||
use crate::semantic_index::FileScopeId;
|
use crate::semantic_index::FileScopeId;
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::system::DbWithWritableSystem as _;
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
use test_case::test_case;
|
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
|
/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
|
||||||
/// the fallback to `typing_extensions` is working correctly.
|
/// the fallback to `typing_extensions` is working correctly.
|
||||||
/// See [`KnownClass::canonical_module`] for more information.
|
/// See [`KnownClass::canonical_module`] for more information.
|
||||||
|
|
@ -12699,6 +12806,40 @@ pub(crate) mod tests {
|
||||||
assert_eq!(intersection.display(&db).to_string(), "Never");
|
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]
|
#[test]
|
||||||
fn type_alias_variance() {
|
fn type_alias_variance() {
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use super::builder::TypeInferenceBuilder;
|
use super::builder::TypeInferenceBuilder;
|
||||||
use crate::db::tests::{TestDb, setup_db};
|
use crate::db::tests::{TestDb, setup_db};
|
||||||
use crate::place::symbol;
|
use crate::place::global_symbol;
|
||||||
use crate::place::{ConsideredDefinitions, Place, global_symbol};
|
|
||||||
use crate::semantic_index::definition::Definition;
|
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::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 crate::types::{KnownClass, KnownInstanceType, check_types};
|
||||||
use ruff_db::diagnostic::Diagnostic;
|
use ruff_db::diagnostic::Diagnostic;
|
||||||
use ruff_db::files::{File, system_path_to_file};
|
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::*;
|
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]
|
#[track_caller]
|
||||||
fn assert_diagnostic_messages(diagnostics: &[Diagnostic], expected: &[&str]) {
|
fn assert_diagnostic_messages(diagnostics: &[Diagnostic], expected: &[&str]) {
|
||||||
let messages: Vec<&str> = diagnostics
|
let messages: Vec<&str> = diagnostics
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue