Compare commits
25 Commits
david/desc
...
david/make
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ab975b142 | ||
|
|
92416e1f85 | ||
|
|
5ecea4e81f | ||
|
|
b3a0353bf2 | ||
|
|
9953dede9e | ||
|
|
fed67170ec | ||
|
|
cc5270ae9c | ||
|
|
c0fc2796a2 | ||
|
|
c5224316c0 | ||
|
|
67087c0417 | ||
|
|
72fe5525ab | ||
|
|
ff290172d7 | ||
|
|
7673b7265d | ||
|
|
caca1874ae | ||
|
|
08f4c60660 | ||
|
|
e86c21e90a | ||
|
|
c84f1e0c72 | ||
|
|
d6ae12c05f | ||
|
|
0743c21811 | ||
|
|
c322baaaef | ||
|
|
ce3dcb066c | ||
|
|
f406835639 | ||
|
|
30383d4855 | ||
|
|
c7d97c3cd5 | ||
|
|
470f852f04 |
@@ -346,12 +346,14 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
// SAFETY: `definition_node` is guaranteed to be a child of `self.module`
|
||||
let kind = unsafe { definition_node.into_owned(self.module.clone()) };
|
||||
let category = kind.category();
|
||||
let is_reexported = kind.is_reexported();
|
||||
let definition = Definition::new(
|
||||
self.db,
|
||||
self.file,
|
||||
self.current_scope(),
|
||||
symbol,
|
||||
kind,
|
||||
is_reexported,
|
||||
countme::Count::default(),
|
||||
);
|
||||
|
||||
|
||||
@@ -33,11 +33,16 @@ pub struct Definition<'db> {
|
||||
/// The symbol defined.
|
||||
pub(crate) symbol: ScopedSymbolId,
|
||||
|
||||
/// WARNING: Only access this field when doing type inference for the same
|
||||
/// file as where `Definition` is defined to avoid cross-file query dependencies.
|
||||
#[no_eq]
|
||||
#[return_ref]
|
||||
#[tracked]
|
||||
pub(crate) kind: DefinitionKind<'db>,
|
||||
|
||||
/// This is a dedicated field to avoid accessing `kind` to compute this value.
|
||||
pub(crate) is_reexported: bool,
|
||||
|
||||
count: countme::Count<Definition<'static>>,
|
||||
}
|
||||
|
||||
@@ -45,22 +50,6 @@ impl<'db> Definition<'db> {
|
||||
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
self.file_scope(db).to_scope_id(db, self.file(db))
|
||||
}
|
||||
|
||||
pub(crate) fn category(self, db: &'db dyn Db) -> DefinitionCategory {
|
||||
self.kind(db).category()
|
||||
}
|
||||
|
||||
pub(crate) fn is_declaration(self, db: &'db dyn Db) -> bool {
|
||||
self.kind(db).category().is_declaration()
|
||||
}
|
||||
|
||||
pub(crate) fn is_binding(self, db: &'db dyn Db) -> bool {
|
||||
self.kind(db).category().is_binding()
|
||||
}
|
||||
|
||||
pub(crate) fn is_reexported(self, db: &'db dyn Db) -> bool {
|
||||
self.kind(db).is_reexported()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
||||
@@ -293,7 +293,7 @@ fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_
|
||||
/// together with boundness information in a [`Symbol`].
|
||||
///
|
||||
/// The type will be a union if there are multiple bindings with different types.
|
||||
pub(crate) fn symbol_from_bindings<'db>(
|
||||
pub(super) fn symbol_from_bindings<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
) -> Symbol<'db> {
|
||||
@@ -479,6 +479,10 @@ fn symbol_impl<'db>(
|
||||
}
|
||||
|
||||
/// Implementation of [`symbol_from_bindings`].
|
||||
///
|
||||
/// ## Implementation Note
|
||||
/// This function gets called cross-module. It, therefore, shouldn't
|
||||
/// access any AST nodes from the file containing the declarations.
|
||||
fn symbol_from_bindings_impl<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
@@ -562,6 +566,10 @@ fn symbol_from_bindings_impl<'db>(
|
||||
}
|
||||
|
||||
/// Implementation of [`symbol_from_declarations`].
|
||||
///
|
||||
/// ## Implementation Note
|
||||
/// This function gets called cross-module. It, therefore, shouldn't
|
||||
/// access any AST nodes from the file containing the declarations.
|
||||
fn symbol_from_declarations_impl<'db>(
|
||||
db: &'db dyn Db,
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
|
||||
@@ -16,7 +16,8 @@ pub(crate) use self::diagnostic::register_lints;
|
||||
pub use self::diagnostic::{TypeCheckDiagnostic, TypeCheckDiagnostics};
|
||||
pub(crate) use self::display::TypeArrayDisplay;
|
||||
pub(crate) use self::infer::{
|
||||
infer_deferred_types, infer_definition_types, infer_expression_types, infer_scope_types,
|
||||
infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types,
|
||||
infer_scope_types,
|
||||
};
|
||||
pub use self::narrow::KnownConstraintFunction;
|
||||
pub(crate) use self::signatures::Signature;
|
||||
@@ -26,7 +27,6 @@ use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::attribute_assignment::AttributeAssignment;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::semantic_index::{
|
||||
attribute_assignments, imported_modules, semantic_index, symbol_table, use_def_map,
|
||||
@@ -1586,7 +1586,10 @@ impl<'db> Type<'db> {
|
||||
.ignore_possibly_unbound()?
|
||||
.try_call(
|
||||
db,
|
||||
&CallArguments::positional([instance.unwrap_or(Type::none(db)), owner]),
|
||||
CallArguments::positional(
|
||||
db,
|
||||
[instance.unwrap_or(Type::none(db)), owner],
|
||||
),
|
||||
)
|
||||
.map(|outcome| Some(outcome.return_type(db)))
|
||||
.unwrap_or(None)
|
||||
@@ -1710,7 +1713,7 @@ impl<'db> Type<'db> {
|
||||
// and a subclass could add a `__bool__` method.
|
||||
|
||||
if let Ok(Type::BooleanLiteral(bool_val)) = self
|
||||
.try_call_dunder(db, "__bool__", &CallArguments::none())
|
||||
.try_call_dunder(db, "__bool__", CallArguments::none(db))
|
||||
.map(|outcome| outcome.return_type(db))
|
||||
{
|
||||
bool_val.into()
|
||||
@@ -1783,7 +1786,7 @@ impl<'db> Type<'db> {
|
||||
return usize_len.try_into().ok().map(Type::IntLiteral);
|
||||
}
|
||||
|
||||
let return_ty = match self.try_call_dunder(db, "__len__", &CallArguments::none()) {
|
||||
let return_ty = match self.try_call_dunder(db, "__len__", CallArguments::none(db)) {
|
||||
Ok(outcome) | Err(CallDunderError::PossiblyUnbound(outcome)) => outcome.return_type(db),
|
||||
|
||||
// TODO: emit a diagnostic
|
||||
@@ -1799,359 +1802,377 @@ impl<'db> Type<'db> {
|
||||
fn try_call(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
arguments: &CallArguments<'_, 'db>,
|
||||
arguments: CallArguments<'db>,
|
||||
) -> Result<CallOutcome<'db>, CallError<'db>> {
|
||||
match self {
|
||||
Type::Callable(CallableType::BoundMethod(bound_method)) => {
|
||||
let instance = bound_method.self_instance(db);
|
||||
let arguments = arguments.with_self(instance);
|
||||
#[salsa::tracked]
|
||||
fn try_call_query<'db>(
|
||||
db: &'db dyn Db,
|
||||
ty_self: Type<'db>,
|
||||
arguments: CallArguments<'db>,
|
||||
) -> Result<CallOutcome<'db>, CallError<'db>> {
|
||||
match ty_self {
|
||||
Type::Callable(CallableType::BoundMethod(bound_method)) => {
|
||||
let instance = bound_method.self_instance(db);
|
||||
let arguments = arguments.with_self(db, instance);
|
||||
|
||||
let binding = bind_call(
|
||||
db,
|
||||
&arguments,
|
||||
bound_method.function(db).signature(db),
|
||||
self,
|
||||
);
|
||||
let binding = bind_call(
|
||||
db,
|
||||
arguments,
|
||||
bound_method.function(db).signature(db),
|
||||
ty_self,
|
||||
);
|
||||
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::Callable(CallableType::MethodWrapperDunderGet(function)) => {
|
||||
// Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`.
|
||||
// This is required because we need to return more precise types than what the signature in
|
||||
// typeshed provides:
|
||||
//
|
||||
// ```py
|
||||
// class FunctionType:
|
||||
// # ...
|
||||
// @overload
|
||||
// def __get__(self, instance: None, owner: type, /) -> FunctionType: ...
|
||||
// @overload
|
||||
// def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ...
|
||||
// ```
|
||||
Type::Callable(CallableType::MethodWrapperDunderGet(function)) => {
|
||||
// Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`.
|
||||
// This is required because we need to return more precise types than what the signature in
|
||||
// typeshed provides:
|
||||
//
|
||||
// ```py
|
||||
// class FunctionType:
|
||||
// # ...
|
||||
// @overload
|
||||
// def __get__(self, instance: None, owner: type, /) -> FunctionType: ...
|
||||
// @overload
|
||||
// def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ...
|
||||
// ```
|
||||
|
||||
let first_argument_is_none =
|
||||
arguments.first_argument().is_some_and(|ty| ty.is_none(db));
|
||||
let first_argument_is_none = arguments
|
||||
.first_argument(db)
|
||||
.is_some_and(|ty| ty.is_none(db));
|
||||
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::new(
|
||||
Some("instance".into()),
|
||||
Some(Type::object(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
),
|
||||
if first_argument_is_none {
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(KnownClass::Type.to_instance(db)),
|
||||
Some("instance".into()),
|
||||
Some(Type::object(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
)
|
||||
} else {
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[KnownClass::Type.to_instance(db), Type::none(db)],
|
||||
)),
|
||||
ParameterKind::PositionalOnly {
|
||||
default_ty: Some(Type::none(db)),
|
||||
},
|
||||
)
|
||||
},
|
||||
]),
|
||||
Some(match arguments.first_argument() {
|
||||
Some(ty) if ty.is_none(db) => Type::FunctionLiteral(function),
|
||||
Some(instance) => Type::Callable(CallableType::BoundMethod(
|
||||
BoundMethodType::new(db, function, instance),
|
||||
)),
|
||||
_ => Type::unknown(),
|
||||
}),
|
||||
);
|
||||
|
||||
let binding = bind_call(db, arguments, &signature, self);
|
||||
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
}
|
||||
}
|
||||
Type::Callable(CallableType::WrapperDescriptorDunderGet) => {
|
||||
// Here, we also model `types.FunctionType.__get__`, but now we consider a call to
|
||||
// this as a function, i.e. we also expect the `self` argument to be passed in.
|
||||
|
||||
let second_argument_is_none =
|
||||
arguments.second_argument().is_some_and(|ty| ty.is_none(db));
|
||||
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::new(
|
||||
Some("self".into()),
|
||||
Some(KnownClass::FunctionType.to_instance(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
),
|
||||
Parameter::new(
|
||||
Some("instance".into()),
|
||||
Some(Type::object(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
),
|
||||
if second_argument_is_none {
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(KnownClass::Type.to_instance(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
)
|
||||
} else {
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[KnownClass::Type.to_instance(db), Type::none(db)],
|
||||
)),
|
||||
ParameterKind::PositionalOnly {
|
||||
default_ty: Some(Type::none(db)),
|
||||
},
|
||||
)
|
||||
},
|
||||
]),
|
||||
Some(
|
||||
match (arguments.first_argument(), arguments.second_argument()) {
|
||||
(Some(function @ Type::FunctionLiteral(_)), Some(instance))
|
||||
if instance.is_none(db) =>
|
||||
{
|
||||
function
|
||||
}
|
||||
(Some(Type::FunctionLiteral(function)), Some(instance)) => {
|
||||
Type::Callable(CallableType::BoundMethod(BoundMethodType::new(
|
||||
db, function, instance,
|
||||
)))
|
||||
}
|
||||
),
|
||||
if first_argument_is_none {
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(KnownClass::Type.to_instance(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
)
|
||||
} else {
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[KnownClass::Type.to_instance(db), Type::none(db)],
|
||||
)),
|
||||
ParameterKind::PositionalOnly {
|
||||
default_ty: Some(Type::none(db)),
|
||||
},
|
||||
)
|
||||
},
|
||||
]),
|
||||
Some(match arguments.first_argument(db) {
|
||||
Some(ty) if ty.is_none(db) => Type::FunctionLiteral(function),
|
||||
Some(instance) => Type::Callable(CallableType::BoundMethod(
|
||||
BoundMethodType::new(db, function, instance),
|
||||
)),
|
||||
_ => Type::unknown(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
let binding = bind_call(db, arguments, &signature, self);
|
||||
let binding = bind_call(db, arguments, &signature, ty_self);
|
||||
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::FunctionLiteral(function_type) => {
|
||||
let mut binding = bind_call(db, arguments, function_type.signature(db), self);
|
||||
Type::Callable(CallableType::WrapperDescriptorDunderGet) => {
|
||||
// Here, we also model `types.FunctionType.__get__`, but now we consider a call to
|
||||
// this as a function, i.e. we also expect the `self` argument to be passed in.
|
||||
|
||||
if binding.has_binding_errors() {
|
||||
return Err(CallError::BindingError { binding });
|
||||
let second_argument_is_none = arguments
|
||||
.second_argument(db)
|
||||
.is_some_and(|ty| ty.is_none(db));
|
||||
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::new(
|
||||
Some("self".into()),
|
||||
Some(KnownClass::FunctionType.to_instance(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
),
|
||||
Parameter::new(
|
||||
Some("instance".into()),
|
||||
Some(Type::object(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
),
|
||||
if second_argument_is_none {
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(KnownClass::Type.to_instance(db)),
|
||||
ParameterKind::PositionalOnly { default_ty: None },
|
||||
)
|
||||
} else {
|
||||
Parameter::new(
|
||||
Some("owner".into()),
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[KnownClass::Type.to_instance(db), Type::none(db)],
|
||||
)),
|
||||
ParameterKind::PositionalOnly {
|
||||
default_ty: Some(Type::none(db)),
|
||||
},
|
||||
)
|
||||
},
|
||||
]),
|
||||
Some(
|
||||
match (arguments.first_argument(db), arguments.second_argument(db)) {
|
||||
(Some(function @ Type::FunctionLiteral(_)), Some(instance))
|
||||
if instance.is_none(db) =>
|
||||
{
|
||||
function
|
||||
}
|
||||
(Some(Type::FunctionLiteral(function)), Some(instance)) => {
|
||||
Type::Callable(CallableType::BoundMethod(BoundMethodType::new(
|
||||
db, function, instance,
|
||||
)))
|
||||
}
|
||||
_ => Type::unknown(),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
let binding = bind_call(db, arguments, &signature, ty_self);
|
||||
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
}
|
||||
}
|
||||
Type::FunctionLiteral(function_type) => {
|
||||
let mut binding =
|
||||
bind_call(db, arguments, function_type.signature(db), ty_self);
|
||||
|
||||
match function_type.known(db) {
|
||||
Some(KnownFunction::IsEquivalentTo) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding
|
||||
.set_return_type(Type::BooleanLiteral(ty_a.is_equivalent_to(db, ty_b)));
|
||||
}
|
||||
Some(KnownFunction::IsSubtypeOf) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding.set_return_type(Type::BooleanLiteral(ty_a.is_subtype_of(db, ty_b)));
|
||||
}
|
||||
Some(KnownFunction::IsAssignableTo) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding
|
||||
.set_return_type(Type::BooleanLiteral(ty_a.is_assignable_to(db, ty_b)));
|
||||
}
|
||||
Some(KnownFunction::IsDisjointFrom) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding
|
||||
.set_return_type(Type::BooleanLiteral(ty_a.is_disjoint_from(db, ty_b)));
|
||||
}
|
||||
Some(KnownFunction::IsGradualEquivalentTo) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding.set_return_type(Type::BooleanLiteral(
|
||||
ty_a.is_gradual_equivalent_to(db, ty_b),
|
||||
));
|
||||
}
|
||||
Some(KnownFunction::IsFullyStatic) => {
|
||||
let ty = binding.one_parameter_type().unwrap_or(Type::unknown());
|
||||
binding.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db)));
|
||||
}
|
||||
Some(KnownFunction::IsSingleton) => {
|
||||
let ty = binding.one_parameter_type().unwrap_or(Type::unknown());
|
||||
binding.set_return_type(Type::BooleanLiteral(ty.is_singleton(db)));
|
||||
}
|
||||
Some(KnownFunction::IsSingleValued) => {
|
||||
let ty = binding.one_parameter_type().unwrap_or(Type::unknown());
|
||||
binding.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db)));
|
||||
if binding.has_binding_errors() {
|
||||
return Err(CallError::BindingError { binding });
|
||||
}
|
||||
|
||||
Some(KnownFunction::Len) => {
|
||||
if let Some(first_arg) = binding.one_parameter_type() {
|
||||
if let Some(len_ty) = first_arg.len(db) {
|
||||
binding.set_return_type(len_ty);
|
||||
}
|
||||
};
|
||||
}
|
||||
match function_type.known(db) {
|
||||
Some(KnownFunction::IsEquivalentTo) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding.set_return_type(Type::BooleanLiteral(
|
||||
ty_a.is_equivalent_to(db, ty_b),
|
||||
));
|
||||
}
|
||||
Some(KnownFunction::IsSubtypeOf) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding.set_return_type(Type::BooleanLiteral(
|
||||
ty_a.is_subtype_of(db, ty_b),
|
||||
));
|
||||
}
|
||||
Some(KnownFunction::IsAssignableTo) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding.set_return_type(Type::BooleanLiteral(
|
||||
ty_a.is_assignable_to(db, ty_b),
|
||||
));
|
||||
}
|
||||
Some(KnownFunction::IsDisjointFrom) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding.set_return_type(Type::BooleanLiteral(
|
||||
ty_a.is_disjoint_from(db, ty_b),
|
||||
));
|
||||
}
|
||||
Some(KnownFunction::IsGradualEquivalentTo) => {
|
||||
let (ty_a, ty_b) = binding
|
||||
.two_parameter_types()
|
||||
.unwrap_or((Type::unknown(), Type::unknown()));
|
||||
binding.set_return_type(Type::BooleanLiteral(
|
||||
ty_a.is_gradual_equivalent_to(db, ty_b),
|
||||
));
|
||||
}
|
||||
Some(KnownFunction::IsFullyStatic) => {
|
||||
let ty = binding.one_parameter_type().unwrap_or(Type::unknown());
|
||||
binding.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db)));
|
||||
}
|
||||
Some(KnownFunction::IsSingleton) => {
|
||||
let ty = binding.one_parameter_type().unwrap_or(Type::unknown());
|
||||
binding.set_return_type(Type::BooleanLiteral(ty.is_singleton(db)));
|
||||
}
|
||||
Some(KnownFunction::IsSingleValued) => {
|
||||
let ty = binding.one_parameter_type().unwrap_or(Type::unknown());
|
||||
binding.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db)));
|
||||
}
|
||||
|
||||
Some(KnownFunction::Repr) => {
|
||||
if let Some(first_arg) = binding.one_parameter_type() {
|
||||
binding.set_return_type(first_arg.repr(db));
|
||||
};
|
||||
}
|
||||
Some(KnownFunction::Len) => {
|
||||
if let Some(first_arg) = binding.one_parameter_type() {
|
||||
if let Some(len_ty) = first_arg.len(db) {
|
||||
binding.set_return_type(len_ty);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Some(KnownFunction::Cast) => {
|
||||
// TODO: Use `.two_parameter_tys()` exclusively
|
||||
// when overloads are supported.
|
||||
if let Some(casted_ty) = arguments.first_argument() {
|
||||
if binding.two_parameter_types().is_some() {
|
||||
binding.set_return_type(casted_ty);
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(KnownFunction::Repr) => {
|
||||
if let Some(first_arg) = binding.one_parameter_type() {
|
||||
binding.set_return_type(first_arg.repr(db));
|
||||
};
|
||||
}
|
||||
|
||||
Some(KnownFunction::GetattrStatic) => {
|
||||
let Some((instance_ty, attr_name, default)) =
|
||||
binding.three_parameter_types()
|
||||
else {
|
||||
return Ok(CallOutcome::Single(binding));
|
||||
};
|
||||
Some(KnownFunction::Cast) => {
|
||||
// TODO: Use `.two_parameter_tys()` exclusively
|
||||
// when overloads are supported.
|
||||
if let Some(casted_ty) = arguments.first_argument(db) {
|
||||
if binding.two_parameter_types().is_some() {
|
||||
binding.set_return_type(casted_ty);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let Some(attr_name) = attr_name.into_string_literal() else {
|
||||
return Ok(CallOutcome::Single(binding));
|
||||
};
|
||||
Some(KnownFunction::GetattrStatic) => {
|
||||
let Some((instance_ty, attr_name, default)) =
|
||||
binding.three_parameter_types()
|
||||
else {
|
||||
return Ok(CallOutcome::Single(binding));
|
||||
};
|
||||
|
||||
let default = if default.is_unknown() {
|
||||
Type::Never
|
||||
} else {
|
||||
default
|
||||
};
|
||||
let Some(attr_name) = attr_name.into_string_literal() else {
|
||||
return Ok(CallOutcome::Single(binding));
|
||||
};
|
||||
|
||||
let union_with_default = |ty| UnionType::from_elements(db, [ty, default]);
|
||||
let default = if default.is_unknown() {
|
||||
Type::Never
|
||||
} else {
|
||||
default
|
||||
};
|
||||
|
||||
// TODO: we could emit a diagnostic here (if default is not set)
|
||||
binding.set_return_type(
|
||||
match instance_ty.static_member(db, attr_name.value(db)) {
|
||||
Symbol::Type(ty, Boundness::Bound) => {
|
||||
if instance_ty.is_fully_static(db) {
|
||||
ty
|
||||
} else {
|
||||
// Here, we attempt to model the fact that an attribute lookup on
|
||||
// a non-fully static type could fail. This is an approximation,
|
||||
// as there are gradual types like `tuple[Any]`, on which a lookup
|
||||
// of (e.g. of the `index` method) would always succeed.
|
||||
let union_with_default =
|
||||
|ty| UnionType::from_elements(db, [ty, default]);
|
||||
|
||||
// TODO: we could emit a diagnostic here (if default is not set)
|
||||
binding.set_return_type(
|
||||
match instance_ty.static_member(db, attr_name.value(db)) {
|
||||
Symbol::Type(ty, Boundness::Bound) => {
|
||||
if instance_ty.is_fully_static(db) {
|
||||
ty
|
||||
} else {
|
||||
// Here, we attempt to model the fact that an attribute lookup on
|
||||
// a non-fully static type could fail. This is an approximation,
|
||||
// as there are gradual types like `tuple[Any]`, on which a lookup
|
||||
// of (e.g. of the `index` method) would always succeed.
|
||||
|
||||
union_with_default(ty)
|
||||
}
|
||||
}
|
||||
Symbol::Type(ty, Boundness::PossiblyUnbound) => {
|
||||
union_with_default(ty)
|
||||
}
|
||||
}
|
||||
Symbol::Type(ty, Boundness::PossiblyUnbound) => {
|
||||
union_with_default(ty)
|
||||
}
|
||||
Symbol::Unbound => default,
|
||||
},
|
||||
);
|
||||
Symbol::Unbound => default,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
}
|
||||
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if binding.has_binding_errors() {
|
||||
Err(CallError::BindingError { binding })
|
||||
} else {
|
||||
Ok(CallOutcome::Single(binding))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
Ok(CallOutcome::Single(CallBinding::from_return_type(
|
||||
match class.known(db) {
|
||||
// If the class is the builtin-bool class (for example `bool(1)`), we try to
|
||||
// return the specific truthiness value of the input arg, `Literal[True]` for
|
||||
// the example above.
|
||||
Some(KnownClass::Bool) => arguments
|
||||
.first_argument()
|
||||
.map(|arg| arg.bool(db).into_type(db))
|
||||
.unwrap_or(Type::BooleanLiteral(false)),
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
Ok(CallOutcome::Single(CallBinding::from_return_type(
|
||||
match class.known(db) {
|
||||
// If the class is the builtin-bool class (for example `bool(1)`), we try to
|
||||
// return the specific truthiness value of the input arg, `Literal[True]` for
|
||||
// the example above.
|
||||
Some(KnownClass::Bool) => arguments
|
||||
.first_argument(db)
|
||||
.map(|arg| arg.bool(db).into_type(db))
|
||||
.unwrap_or(Type::BooleanLiteral(false)),
|
||||
|
||||
// TODO: Don't ignore the second and third arguments to `str`
|
||||
// https://github.com/astral-sh/ruff/pull/16161#discussion_r1958425568
|
||||
Some(KnownClass::Str) => arguments
|
||||
.first_argument()
|
||||
.map(|arg| arg.str(db))
|
||||
.unwrap_or(Type::string_literal(db, "")),
|
||||
// TODO: Don't ignore the second and third arguments to `str`
|
||||
// https://github.com/astral-sh/ruff/pull/16161#discussion_r1958425568
|
||||
Some(KnownClass::Str) => arguments
|
||||
.first_argument(db)
|
||||
.map(|arg| arg.str(db))
|
||||
.unwrap_or(Type::string_literal(db, "")),
|
||||
|
||||
_ => Type::Instance(InstanceType { class }),
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
instance_ty @ Type::Instance(_) => {
|
||||
instance_ty
|
||||
.try_call_dunder(db, "__call__", arguments)
|
||||
.map_err(|err| match err {
|
||||
CallDunderError::Call(CallError::NotCallable { .. }) => {
|
||||
// Turn "`<type of illegal '__call__'>` not callable" into
|
||||
// "`X` not callable"
|
||||
CallError::NotCallable {
|
||||
not_callable_ty: self,
|
||||
}
|
||||
}
|
||||
CallDunderError::Call(CallError::Union {
|
||||
called_ty: _,
|
||||
bindings,
|
||||
errors,
|
||||
}) => CallError::Union {
|
||||
called_ty: self,
|
||||
bindings,
|
||||
errors,
|
||||
_ => Type::Instance(InstanceType { class }),
|
||||
},
|
||||
CallDunderError::Call(error) => error,
|
||||
// Turn "possibly unbound object of type `Literal['__call__']`"
|
||||
// into "`X` not callable (possibly unbound `__call__` method)"
|
||||
CallDunderError::PossiblyUnbound(outcome) => {
|
||||
CallError::PossiblyUnboundDunderCall {
|
||||
called_type: self,
|
||||
outcome: Box::new(outcome),
|
||||
)))
|
||||
}
|
||||
|
||||
instance_ty @ Type::Instance(_) => {
|
||||
instance_ty
|
||||
.try_call_dunder(db, "__call__", arguments)
|
||||
.map_err(|err| match err {
|
||||
CallDunderError::Call(CallError::NotCallable { .. }) => {
|
||||
// Turn "`<type of illegal '__call__'>` not callable" into
|
||||
// "`X` not callable"
|
||||
CallError::NotCallable {
|
||||
not_callable_ty: ty_self,
|
||||
}
|
||||
}
|
||||
}
|
||||
CallDunderError::MethodNotAvailable => {
|
||||
// Turn "`X.__call__` unbound" into "`X` not callable"
|
||||
CallError::NotCallable {
|
||||
not_callable_ty: self,
|
||||
CallDunderError::Call(CallError::Union {
|
||||
called_ty: _,
|
||||
bindings,
|
||||
errors,
|
||||
}) => CallError::Union {
|
||||
called_ty: ty_self,
|
||||
bindings,
|
||||
errors,
|
||||
},
|
||||
CallDunderError::Call(error) => error,
|
||||
// Turn "possibly unbound object of type `Literal['__call__']`"
|
||||
// into "`X` not callable (possibly unbound `__call__` method)"
|
||||
CallDunderError::PossiblyUnbound(outcome) => {
|
||||
CallError::PossiblyUnboundDunderCall {
|
||||
called_type: ty_self,
|
||||
outcome: Box::new(outcome),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
CallDunderError::MethodNotAvailable => {
|
||||
// Turn "`X.__call__` unbound" into "`X` not callable"
|
||||
CallError::NotCallable {
|
||||
not_callable_ty: ty_self,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Dynamic types are callable, and the return type is the same dynamic type
|
||||
Type::Dynamic(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(ty_self))),
|
||||
|
||||
Type::Union(union) => CallOutcome::try_call_union(db, union, |element| {
|
||||
element.try_call(db, arguments)
|
||||
}),
|
||||
|
||||
Type::Intersection(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(
|
||||
todo_type!("Type::Intersection.call()"),
|
||||
))),
|
||||
|
||||
_ => Err(CallError::NotCallable {
|
||||
not_callable_ty: ty_self,
|
||||
}),
|
||||
}
|
||||
|
||||
// Dynamic types are callable, and the return type is the same dynamic type
|
||||
Type::Dynamic(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(self))),
|
||||
|
||||
Type::Union(union) => {
|
||||
CallOutcome::try_call_union(db, union, |element| element.try_call(db, arguments))
|
||||
}
|
||||
|
||||
Type::Intersection(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(
|
||||
todo_type!("Type::Intersection.call()"),
|
||||
))),
|
||||
|
||||
_ => Err(CallError::NotCallable {
|
||||
not_callable_ty: self,
|
||||
}),
|
||||
}
|
||||
|
||||
try_call_query(db, self, arguments)
|
||||
}
|
||||
|
||||
/// Return the outcome of calling an class/instance attribute of this type
|
||||
@@ -2164,13 +2185,13 @@ impl<'db> Type<'db> {
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
receiver_ty: &Type<'db>,
|
||||
arguments: &CallArguments<'_, 'db>,
|
||||
arguments: CallArguments<'db>,
|
||||
) -> Result<CallOutcome<'db>, CallError<'db>> {
|
||||
match self {
|
||||
Type::FunctionLiteral(..) => {
|
||||
// Functions are always descriptors, so this would effectively call
|
||||
// the function with the instance as the first argument
|
||||
self.try_call(db, &arguments.with_self(*receiver_ty))
|
||||
self.try_call(db, arguments.with_self(db, *receiver_ty))
|
||||
}
|
||||
|
||||
Type::Instance(_) | Type::ClassLiteral(_) => self.try_call(db, arguments),
|
||||
@@ -2197,7 +2218,7 @@ impl<'db> Type<'db> {
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
arguments: &CallArguments<'_, 'db>,
|
||||
arguments: CallArguments<'db>,
|
||||
) -> Result<CallOutcome<'db>, CallDunderError<'db>> {
|
||||
match self.to_meta_type(db).member(db, name) {
|
||||
Symbol::Type(callable_ty, Boundness::Bound) => {
|
||||
@@ -2226,12 +2247,12 @@ impl<'db> Type<'db> {
|
||||
};
|
||||
}
|
||||
|
||||
let dunder_iter_result = self.try_call_dunder(db, "__iter__", &CallArguments::none());
|
||||
let dunder_iter_result = self.try_call_dunder(db, "__iter__", CallArguments::none(db));
|
||||
match &dunder_iter_result {
|
||||
Ok(outcome) | Err(CallDunderError::PossiblyUnbound(outcome)) => {
|
||||
let iterator_ty = outcome.return_type(db);
|
||||
|
||||
return match iterator_ty.try_call_dunder(db, "__next__", &CallArguments::none()) {
|
||||
return match iterator_ty.try_call_dunder(db, "__next__", CallArguments::none(db)) {
|
||||
Ok(outcome) => {
|
||||
if matches!(
|
||||
dunder_iter_result,
|
||||
@@ -2279,7 +2300,7 @@ impl<'db> Type<'db> {
|
||||
match self.try_call_dunder(
|
||||
db,
|
||||
"__getitem__",
|
||||
&CallArguments::positional([KnownClass::Int.to_instance(db)]),
|
||||
CallArguments::positional(db, [KnownClass::Int.to_instance(db)]),
|
||||
) {
|
||||
Ok(outcome) => IterationOutcome::Iterable {
|
||||
element_ty: outcome.return_type(db),
|
||||
@@ -4148,9 +4169,9 @@ impl<'db> Class<'db> {
|
||||
let namespace = KnownClass::Dict.to_instance(db);
|
||||
|
||||
// TODO: Other keyword arguments?
|
||||
let arguments = CallArguments::positional([name, bases, namespace]);
|
||||
let arguments = CallArguments::positional(db, [name, bases, namespace]);
|
||||
|
||||
let return_ty_result = match metaclass.try_call(db, &arguments) {
|
||||
let return_ty_result = match metaclass.try_call(db, arguments) {
|
||||
Ok(outcome) => Ok(outcome.return_type(db)),
|
||||
|
||||
Err(CallError::NotCallable { not_callable_ty }) => Err(MetaclassError {
|
||||
@@ -4337,16 +4358,6 @@ impl<'db> Class<'db> {
|
||||
name: &str,
|
||||
inferred_type_from_class_body: Option<Type<'db>>,
|
||||
) -> Symbol<'db> {
|
||||
// We use a separate salsa query here to prevent unrelated changes in the AST of an external
|
||||
// file from triggering re-evaluations of downstream queries.
|
||||
// See the `dependency_implicit_instance_attribute` test for more information.
|
||||
#[salsa::tracked]
|
||||
fn infer_expression_type<'db>(db: &'db dyn Db, expression: Expression<'db>) -> Type<'db> {
|
||||
let inference = infer_expression_types(db, expression);
|
||||
let expr_scope = expression.scope(db);
|
||||
inference.expression_type(expression.node_ref(db).scoped_expression_id(db, expr_scope))
|
||||
}
|
||||
|
||||
// If we do not see any declarations of an attribute, neither in the class body nor in
|
||||
// any method, we build a union of `Unknown` with the inferred types of all bindings of
|
||||
// that attribute. We include `Unknown` in that union to account for the fact that the
|
||||
|
||||
@@ -11,7 +11,7 @@ pub(super) use bind::{bind_call, CallBinding};
|
||||
/// A successfully bound call where all arguments are valid.
|
||||
///
|
||||
/// It's guaranteed that the wrapped bindings have no errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
|
||||
pub(super) enum CallOutcome<'db> {
|
||||
/// The call resolves to exactly one binding.
|
||||
Single(CallBinding<'db>),
|
||||
@@ -84,7 +84,7 @@ impl<'db> CallOutcome<'db> {
|
||||
}
|
||||
|
||||
/// The reason why calling a type failed.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
|
||||
pub(super) enum CallError<'db> {
|
||||
/// The type is not callable.
|
||||
NotCallable {
|
||||
|
||||
@@ -1,63 +1,60 @@
|
||||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::Db;
|
||||
|
||||
use super::Type;
|
||||
|
||||
/// Typed arguments for a single call, in source order.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct CallArguments<'a, 'db>(Vec<Argument<'a, 'db>>);
|
||||
#[salsa::tracked]
|
||||
pub(crate) struct CallArguments<'db> {
|
||||
args: Vec<Argument<'db>>,
|
||||
}
|
||||
|
||||
impl<'a, 'db> CallArguments<'a, 'db> {
|
||||
impl<'a, 'db> CallArguments<'db> {
|
||||
/// Create a [`CallArguments`] with no arguments.
|
||||
pub(crate) fn none() -> Self {
|
||||
Self(Vec::new())
|
||||
pub(crate) fn none(db: &'db dyn Db) -> Self {
|
||||
CallArguments::new(db, Vec::new())
|
||||
}
|
||||
|
||||
/// Create a [`CallArguments`] from an iterator over non-variadic positional argument types.
|
||||
pub(crate) fn positional(positional_tys: impl IntoIterator<Item = Type<'db>>) -> Self {
|
||||
positional_tys
|
||||
.into_iter()
|
||||
.map(Argument::Positional)
|
||||
.collect()
|
||||
pub(crate) fn positional(
|
||||
db: &'db dyn Db,
|
||||
positional_tys: impl IntoIterator<Item = Type<'db>>,
|
||||
) -> Self {
|
||||
CallArguments::new(
|
||||
db,
|
||||
positional_tys
|
||||
.into_iter()
|
||||
.map(Argument::Positional)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Prepend an extra positional argument.
|
||||
pub(crate) fn with_self(&self, self_ty: Type<'db>) -> Self {
|
||||
let mut arguments = Vec::with_capacity(self.0.len() + 1);
|
||||
pub(crate) fn with_self(&self, db: &'db dyn Db, self_ty: Type<'db>) -> Self {
|
||||
let mut arguments = Vec::with_capacity(self.args(db).len() + 1);
|
||||
arguments.push(Argument::Synthetic(self_ty));
|
||||
arguments.extend_from_slice(&self.0);
|
||||
Self(arguments)
|
||||
arguments.extend_from_slice(&self.args(db));
|
||||
CallArguments::new(db, arguments)
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = &Argument<'a, 'db>> {
|
||||
self.0.iter()
|
||||
pub(crate) fn args_iter(&self, db: &'db dyn Db) -> impl IntoIterator<Item = Argument<'db>> {
|
||||
self.args(db).into_iter()
|
||||
}
|
||||
|
||||
// TODO this should be eliminated in favor of [`bind_call`]
|
||||
pub(crate) fn first_argument(&self) -> Option<Type<'db>> {
|
||||
self.0.first().map(Argument::ty)
|
||||
pub(crate) fn first_argument(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
self.args(db).first().map(Argument::ty)
|
||||
}
|
||||
|
||||
// TODO this should be eliminated in favor of [`bind_call`]
|
||||
pub(crate) fn second_argument(&self) -> Option<Type<'db>> {
|
||||
self.0.get(1).map(Argument::ty)
|
||||
pub(crate) fn second_argument(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
self.args(db).get(1).map(Argument::ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db, 'a, 'b> IntoIterator for &'b CallArguments<'a, 'db> {
|
||||
type Item = &'b Argument<'a, 'db>;
|
||||
type IntoIter = std::slice::Iter<'b, Argument<'a, 'db>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'db> FromIterator<Argument<'a, 'db>> for CallArguments<'a, 'db> {
|
||||
fn from_iter<T: IntoIterator<Item = Argument<'a, 'db>>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Argument<'a, 'db> {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub(crate) enum Argument<'db> {
|
||||
/// The synthetic `self` or `cls` argument, which doesn't appear explicitly at the call site.
|
||||
Synthetic(Type<'db>),
|
||||
/// A positional argument.
|
||||
@@ -65,12 +62,12 @@ pub(crate) enum Argument<'a, 'db> {
|
||||
/// A starred positional argument (e.g. `*args`).
|
||||
Variadic(Type<'db>),
|
||||
/// A keyword argument (e.g. `a=1`).
|
||||
Keyword { name: &'a str, ty: Type<'db> },
|
||||
Keyword { name: Name, ty: Type<'db> },
|
||||
/// The double-starred keywords argument (e.g. `**kwargs`).
|
||||
Keywords(Type<'db>),
|
||||
}
|
||||
|
||||
impl<'db> Argument<'_, 'db> {
|
||||
impl<'db> Argument<'db> {
|
||||
fn ty(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::Synthetic(ty) => *ty,
|
||||
|
||||
@@ -16,7 +16,7 @@ use ruff_text_size::Ranged;
|
||||
/// parameters, and any errors resulting from binding the call.
|
||||
pub(crate) fn bind_call<'db>(
|
||||
db: &'db dyn Db,
|
||||
arguments: &CallArguments<'_, 'db>,
|
||||
arguments: CallArguments<'db>,
|
||||
signature: &Signature<'db>,
|
||||
callable_ty: Type<'db>,
|
||||
) -> CallBinding<'db> {
|
||||
@@ -38,7 +38,7 @@ pub(crate) fn bind_call<'db>(
|
||||
None
|
||||
}
|
||||
};
|
||||
for (argument_index, argument) in arguments.iter().enumerate() {
|
||||
for (argument_index, argument) in arguments.args_iter(db).into_iter().enumerate() {
|
||||
let (index, parameter, argument_ty, positional) = match argument {
|
||||
Argument::Positional(ty) | Argument::Synthetic(ty) => {
|
||||
if matches!(argument, Argument::Synthetic(_)) {
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn bind_call<'db>(
|
||||
}
|
||||
Argument::Keyword { name, ty } => {
|
||||
let Some((index, parameter)) = parameters
|
||||
.keyword_by_name(name)
|
||||
.keyword_by_name(&name)
|
||||
.or_else(|| parameters.keyword_variadic())
|
||||
else {
|
||||
errors.push(CallBindingError::UnknownArgument {
|
||||
@@ -81,13 +81,13 @@ pub(crate) fn bind_call<'db>(
|
||||
parameter: ParameterContext::new(parameter, index, positional),
|
||||
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||
expected_ty,
|
||||
provided_ty: *argument_ty,
|
||||
provided_ty: argument_ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(existing) = parameter_tys[index].replace(*argument_ty) {
|
||||
if let Some(existing) = parameter_tys[index].replace(argument_ty) {
|
||||
if parameter.is_variadic() || parameter.is_keyword_variadic() {
|
||||
let union = UnionType::from_elements(db, [existing, *argument_ty]);
|
||||
let union = UnionType::from_elements(db, [existing, argument_ty]);
|
||||
parameter_tys[index].replace(union);
|
||||
} else {
|
||||
errors.push(CallBindingError::ParameterAlreadyAssigned {
|
||||
@@ -137,7 +137,7 @@ pub(crate) fn bind_call<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
|
||||
pub(crate) struct CallBinding<'db> {
|
||||
/// Type of the callable object (function, class...)
|
||||
callable_ty: Type<'db>,
|
||||
@@ -273,7 +273,7 @@ impl std::fmt::Display for ParameterContexts {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
|
||||
pub(crate) enum CallBindingError<'db> {
|
||||
/// The type of an argument is not assignable to the annotated type of its corresponding
|
||||
/// parameter.
|
||||
|
||||
@@ -118,7 +118,7 @@ fn infer_definition_types_cycle_recovery<'db>(
|
||||
) -> TypeInference<'db> {
|
||||
tracing::trace!("infer_definition_types_cycle_recovery");
|
||||
let mut inference = TypeInference::empty(input.scope(db));
|
||||
let category = input.category(db);
|
||||
let category = input.kind(db).category();
|
||||
if category.is_declaration() {
|
||||
inference
|
||||
.declarations
|
||||
@@ -198,6 +198,36 @@ pub(crate) fn infer_expression_types<'db>(
|
||||
TypeInferenceBuilder::new(db, InferenceRegion::Expression(expression), index).finish()
|
||||
}
|
||||
|
||||
/// Infers the type of an `expression` that is guaranteed to be in the same file as the calling query.
|
||||
///
|
||||
/// This is a small helper around [`infer_expression_types()`] to reduce the boilerplate.
|
||||
/// Use [`infer_expression_type()`] if it isn't guaranteed that `expression` is in the same file to
|
||||
/// avoid cross-file query dependencies.
|
||||
pub(super) fn infer_same_file_expression_type<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
) -> Type<'db> {
|
||||
let inference = infer_expression_types(db, expression);
|
||||
let scope = expression.scope(db);
|
||||
inference.expression_type(expression.node_ref(db).scoped_expression_id(db, scope))
|
||||
}
|
||||
|
||||
/// Infers the type of an expression where the expression might come from another file.
|
||||
///
|
||||
/// Use this over [`infer_expression_types`] if the expression might come from another file than the
|
||||
/// enclosing query to avoid cross-file query dependencies.
|
||||
///
|
||||
/// Use [`infer_same_file_expression_type`] if it is guaranteed that `expression` is in the same
|
||||
/// to avoid unnecessary salsa ingredients. This is normally the case inside the `TypeInferenceBuilder`.
|
||||
#[salsa::tracked]
|
||||
pub(crate) fn infer_expression_type<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
) -> Type<'db> {
|
||||
// It's okay to call the "same file" version here because we're inside a salsa query.
|
||||
infer_same_file_expression_type(db, expression)
|
||||
}
|
||||
|
||||
/// Infer the types for an [`Unpack`] operation.
|
||||
///
|
||||
/// This infers the expression type and performs structural match against the target expression
|
||||
@@ -870,7 +900,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, node: AnyNodeRef, binding: Definition<'db>, ty: Type<'db>) {
|
||||
debug_assert!(binding.is_binding(self.db()));
|
||||
debug_assert!(binding.kind(self.db()).category().is_binding());
|
||||
let use_def = self.index.use_def_map(binding.file_scope(self.db()));
|
||||
let declarations = use_def.declarations_at_binding(binding);
|
||||
let mut bound_ty = ty;
|
||||
@@ -905,7 +935,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
declaration: Definition<'db>,
|
||||
ty: TypeAndQualifiers<'db>,
|
||||
) {
|
||||
debug_assert!(declaration.is_declaration(self.db()));
|
||||
debug_assert!(declaration.kind(self.db()).category().is_declaration());
|
||||
let use_def = self.index.use_def_map(declaration.file_scope(self.db()));
|
||||
let prior_bindings = use_def.bindings_at_declaration(declaration);
|
||||
// unbound_ty is Never because for this check we don't care about unbound
|
||||
@@ -935,8 +965,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
definition: Definition<'db>,
|
||||
declared_and_inferred_ty: &DeclaredAndInferredType<'db>,
|
||||
) {
|
||||
debug_assert!(definition.is_binding(self.db()));
|
||||
debug_assert!(definition.is_declaration(self.db()));
|
||||
debug_assert!(definition.kind(self.db()).category().is_binding());
|
||||
debug_assert!(definition.kind(self.db()).category().is_declaration());
|
||||
|
||||
let (declared_ty, inferred_ty) = match *declared_and_inferred_ty {
|
||||
DeclaredAndInferredType::AreTheSame(ty) => (ty.into(), ty),
|
||||
@@ -1616,7 +1646,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
let target_ty = enter_ty
|
||||
.try_call(self.db(), &CallArguments::positional([context_expression_ty]))
|
||||
.try_call(self.db(), CallArguments::positional(self.db(), [context_expression_ty]))
|
||||
.map(|outcome| outcome.return_type(self.db()))
|
||||
.unwrap_or_else(|err| {
|
||||
// TODO: Use more specific error messages for the different error cases.
|
||||
@@ -1661,12 +1691,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
if exit_ty
|
||||
.try_call(
|
||||
self.db(),
|
||||
&CallArguments::positional([
|
||||
context_manager_ty,
|
||||
Type::none(self.db()),
|
||||
Type::none(self.db()),
|
||||
Type::none(self.db()),
|
||||
]),
|
||||
CallArguments::positional(
|
||||
self.db(),
|
||||
[
|
||||
context_manager_ty,
|
||||
Type::none(self.db()),
|
||||
Type::none(self.db()),
|
||||
Type::none(self.db()),
|
||||
],
|
||||
),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
@@ -2211,7 +2244,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
{
|
||||
let call = class_member.try_call(
|
||||
self.db(),
|
||||
&CallArguments::positional([target_type, value_type]),
|
||||
CallArguments::positional(self.db(), [target_type, value_type]),
|
||||
);
|
||||
let augmented_return_ty = match call {
|
||||
Ok(t) => t.return_type(self.db()),
|
||||
@@ -2692,46 +2725,53 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
&mut self,
|
||||
arguments: &'a ast::Arguments,
|
||||
parameter_expectations: ParameterExpectations,
|
||||
) -> CallArguments<'a, 'db> {
|
||||
arguments
|
||||
.arguments_source_order()
|
||||
.enumerate()
|
||||
.map(|(index, arg_or_keyword)| {
|
||||
let infer_argument_type = match parameter_expectations.expectation_at_index(index) {
|
||||
ParameterExpectation::TypeExpression => Self::infer_type_expression,
|
||||
ParameterExpectation::ValueExpression => Self::infer_expression,
|
||||
};
|
||||
) -> CallArguments<'db> {
|
||||
CallArguments::new(
|
||||
self.db(),
|
||||
arguments
|
||||
.arguments_source_order()
|
||||
.enumerate()
|
||||
.map(|(index, arg_or_keyword)| {
|
||||
let infer_argument_type =
|
||||
match parameter_expectations.expectation_at_index(index) {
|
||||
ParameterExpectation::TypeExpression => Self::infer_type_expression,
|
||||
ParameterExpectation::ValueExpression => Self::infer_expression,
|
||||
};
|
||||
|
||||
match arg_or_keyword {
|
||||
ast::ArgOrKeyword::Arg(arg) => match arg {
|
||||
ast::Expr::Starred(ast::ExprStarred {
|
||||
match arg_or_keyword {
|
||||
ast::ArgOrKeyword::Arg(arg) => match arg {
|
||||
ast::Expr::Starred(ast::ExprStarred {
|
||||
value,
|
||||
range: _,
|
||||
ctx: _,
|
||||
}) => {
|
||||
let ty = infer_argument_type(self, value);
|
||||
self.store_expression_type(arg, ty);
|
||||
Argument::Variadic(ty)
|
||||
}
|
||||
// TODO diagnostic if after a keyword argument
|
||||
_ => Argument::Positional(infer_argument_type(self, arg)),
|
||||
},
|
||||
ast::ArgOrKeyword::Keyword(ast::Keyword {
|
||||
arg,
|
||||
value,
|
||||
range: _,
|
||||
ctx: _,
|
||||
}) => {
|
||||
let ty = infer_argument_type(self, value);
|
||||
self.store_expression_type(arg, ty);
|
||||
Argument::Variadic(ty)
|
||||
}
|
||||
// TODO diagnostic if after a keyword argument
|
||||
_ => Argument::Positional(infer_argument_type(self, arg)),
|
||||
},
|
||||
ast::ArgOrKeyword::Keyword(ast::Keyword {
|
||||
arg,
|
||||
value,
|
||||
range: _,
|
||||
}) => {
|
||||
let ty = infer_argument_type(self, value);
|
||||
if let Some(arg) = arg {
|
||||
Argument::Keyword { name: &arg.id, ty }
|
||||
} else {
|
||||
// TODO diagnostic if not last
|
||||
Argument::Keywords(ty)
|
||||
if let Some(arg) = arg {
|
||||
Argument::Keyword {
|
||||
name: arg.id.clone(),
|
||||
ty,
|
||||
}
|
||||
} else {
|
||||
// TODO diagnostic if not last
|
||||
Argument::Keywords(ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn infer_optional_expression(&mut self, expression: Option<&ast::Expr>) -> Option<Type<'db>> {
|
||||
@@ -3247,7 +3287,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.unwrap_or_default();
|
||||
|
||||
let call_arguments = self.infer_arguments(arguments, parameter_expectations);
|
||||
let call = function_type.try_call(self.db(), &call_arguments);
|
||||
let call = function_type.try_call(self.db(), call_arguments);
|
||||
|
||||
match call {
|
||||
Ok(outcome) => {
|
||||
@@ -3751,7 +3791,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
match operand_type.try_call_dunder(
|
||||
self.db(),
|
||||
unary_dunder_method,
|
||||
&CallArguments::none(),
|
||||
CallArguments::none(self.db()),
|
||||
) {
|
||||
Ok(outcome) => outcome.return_type(self.db()),
|
||||
Err(e) => {
|
||||
@@ -4002,7 +4042,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.try_call_dunder(
|
||||
self.db(),
|
||||
reflected_dunder,
|
||||
&CallArguments::positional([left_ty]),
|
||||
CallArguments::positional(self.db(), [left_ty]),
|
||||
)
|
||||
.map(|outcome| outcome.return_type(self.db()))
|
||||
.or_else(|_| {
|
||||
@@ -4010,7 +4050,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.try_call_dunder(
|
||||
self.db(),
|
||||
op.dunder(),
|
||||
&CallArguments::positional([right_ty]),
|
||||
CallArguments::positional(self.db(), [right_ty]),
|
||||
)
|
||||
.map(|outcome| outcome.return_type(self.db()))
|
||||
})
|
||||
@@ -4023,7 +4063,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
left_class.member(self.db(), op.dunder())
|
||||
{
|
||||
class_member
|
||||
.try_call(self.db(), &CallArguments::positional([left_ty, right_ty]))
|
||||
.try_call(
|
||||
self.db(),
|
||||
CallArguments::positional(self.db(), [left_ty, right_ty]),
|
||||
)
|
||||
.map(|outcome| outcome.return_type(self.db()))
|
||||
.ok()
|
||||
} else {
|
||||
@@ -4041,7 +4084,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
class_member
|
||||
.try_call(
|
||||
self.db(),
|
||||
&CallArguments::positional([right_ty, left_ty]),
|
||||
CallArguments::positional(self.db(), [right_ty, left_ty]),
|
||||
)
|
||||
.map(|outcome| outcome.return_type(self.db()))
|
||||
.ok()
|
||||
@@ -4610,21 +4653,23 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let db = self.db();
|
||||
// The following resource has details about the rich comparison algorithm:
|
||||
// https://snarky.ca/unravelling-rich-comparison-operators/
|
||||
let call_dunder = |op: RichCompareOperator,
|
||||
left: InstanceType<'db>,
|
||||
right: InstanceType<'db>| {
|
||||
// TODO: How do we want to handle possibly unbound dunder methods?
|
||||
match left.class.class_member(db, op.dunder()) {
|
||||
Symbol::Type(class_member_dunder, Boundness::Bound) => class_member_dunder
|
||||
.try_call(
|
||||
db,
|
||||
&CallArguments::positional([Type::Instance(left), Type::Instance(right)]),
|
||||
)
|
||||
.map(|outcome| outcome.return_type(db))
|
||||
.ok(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let call_dunder =
|
||||
|op: RichCompareOperator, left: InstanceType<'db>, right: InstanceType<'db>| {
|
||||
// TODO: How do we want to handle possibly unbound dunder methods?
|
||||
match left.class.class_member(db, op.dunder()) {
|
||||
Symbol::Type(class_member_dunder, Boundness::Bound) => class_member_dunder
|
||||
.try_call(
|
||||
db,
|
||||
CallArguments::positional(
|
||||
db,
|
||||
[Type::Instance(left), Type::Instance(right)],
|
||||
),
|
||||
)
|
||||
.map(|outcome| outcome.return_type(db))
|
||||
.ok(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
// The reflected dunder has priority if the right-hand side is a strict subclass of the left-hand side.
|
||||
if left != right && right.is_subtype_of(db, left) {
|
||||
@@ -4668,7 +4713,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
contains_dunder
|
||||
.try_call(
|
||||
db,
|
||||
&CallArguments::positional([Type::Instance(right), Type::Instance(left)]),
|
||||
CallArguments::positional(
|
||||
db,
|
||||
[Type::Instance(right), Type::Instance(left)],
|
||||
),
|
||||
)
|
||||
.map(|outcome| outcome.return_type(db))
|
||||
.ok()
|
||||
@@ -4926,7 +4974,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
match value_ty.try_call_dunder(
|
||||
self.db(),
|
||||
"__getitem__",
|
||||
&CallArguments::positional([slice_ty]),
|
||||
CallArguments::positional(self.db(), [slice_ty]),
|
||||
) {
|
||||
Ok(outcome) => return outcome.return_type(self.db()),
|
||||
Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
|
||||
@@ -4987,7 +5035,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
return ty
|
||||
.try_call(self.db(), &CallArguments::positional([value_ty, slice_ty]))
|
||||
.try_call(self.db(), CallArguments::positional(self.db(),[value_ty, slice_ty]))
|
||||
.map(|outcome| outcome.return_type(self.db()))
|
||||
.unwrap_or_else(|err| {
|
||||
self.context.report_lint(
|
||||
@@ -6629,4 +6677,93 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This test verifies that changing a class's declaration in a non-meaningful way (e.g. by adding a comment)
|
||||
/// doesn't trigger type inference for expressions that depend on the class's members.
|
||||
#[test]
|
||||
fn dependency_own_instance_member() -> anyhow::Result<()> {
|
||||
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
|
||||
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
|
||||
let ast = parsed_module(db, file_main);
|
||||
// Get the second statement in `main.py` (x = …) and extract the expression
|
||||
// node on the right-hand side:
|
||||
let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
|
||||
|
||||
let index = semantic_index(db, file_main);
|
||||
index.expression(x_rhs_node.as_ref())
|
||||
}
|
||||
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
if random.choice([True, False]):
|
||||
attr: int = 42
|
||||
else:
|
||||
attr: None = None
|
||||
"#,
|
||||
)?;
|
||||
db.write_dedented(
|
||||
"/src/main.py",
|
||||
r#"
|
||||
from mod import C
|
||||
x = C().attr
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
|
||||
|
||||
// Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
if random.choice([True, False]):
|
||||
attr: str = "42"
|
||||
else:
|
||||
attr: None = None
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
|
||||
|
||||
// Add a comment; this should not trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
# comment
|
||||
if random.choice([True, False]):
|
||||
attr: str = "42"
|
||||
else:
|
||||
attr: None = None
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
infer_expression_types,
|
||||
x_rhs_expression(&db),
|
||||
&events,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
use crate::semantic_index::symbol_table;
|
||||
use crate::types::infer::infer_same_file_expression_type;
|
||||
use crate::types::{
|
||||
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass, KnownFunction,
|
||||
SubclassOfType, Truthiness, Type, UnionBuilder,
|
||||
@@ -497,11 +498,8 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
if let Some(ast::ExprName { id, .. }) = subject.node_ref(self.db).as_name_expr() {
|
||||
// SAFETY: we should always have a symbol for every Name node.
|
||||
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
|
||||
let scope = self.scope();
|
||||
let inference = infer_expression_types(self.db, cls);
|
||||
let ty = inference
|
||||
.expression_type(cls.node_ref(self.db).scoped_expression_id(self.db, scope))
|
||||
.to_instance(self.db);
|
||||
let ty = infer_same_file_expression_type(self.db, cls).to_instance(self.db);
|
||||
|
||||
let mut constraints = NarrowingConstraints::default();
|
||||
constraints.insert(symbol, ty);
|
||||
Some(constraints)
|
||||
|
||||
@@ -178,11 +178,8 @@ use std::cmp::Ordering;
|
||||
use ruff_index::{Idx, IndexVec};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::semantic_index::{
|
||||
ast_ids::HasScopedExpressionId,
|
||||
constraint::{Constraint, ConstraintNode, PatternConstraintKind},
|
||||
};
|
||||
use crate::types::{infer_expression_types, Truthiness};
|
||||
use crate::semantic_index::constraint::{Constraint, ConstraintNode, PatternConstraintKind};
|
||||
use crate::types::{infer_expression_type, Truthiness};
|
||||
use crate::Db;
|
||||
|
||||
/// A ternary formula that defines under what conditions a binding is visible. (A ternary formula
|
||||
@@ -617,28 +614,14 @@ impl<'db> VisibilityConstraints<'db> {
|
||||
fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness {
|
||||
match constraint.node {
|
||||
ConstraintNode::Expression(test_expr) => {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
let ty = inference
|
||||
.expression_type(test_expr.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
let ty = infer_expression_type(db, test_expr);
|
||||
ty.bool(db).negate_if(!constraint.is_positive)
|
||||
}
|
||||
ConstraintNode::Pattern(inner) => match inner.kind(db) {
|
||||
PatternConstraintKind::Value(value, guard) => {
|
||||
let subject_expression = inner.subject(db);
|
||||
let inference = infer_expression_types(db, subject_expression);
|
||||
let scope = subject_expression.scope(db);
|
||||
let subject_ty = inference.expression_type(
|
||||
subject_expression
|
||||
.node_ref(db)
|
||||
.scoped_expression_id(db, scope),
|
||||
);
|
||||
|
||||
let inference = infer_expression_types(db, *value);
|
||||
let scope = value.scope(db);
|
||||
let value_ty = inference
|
||||
.expression_type(value.node_ref(db).scoped_expression_id(db, scope));
|
||||
let subject_ty = infer_expression_type(db, subject_expression);
|
||||
let value_ty = infer_expression_type(db, *value);
|
||||
|
||||
if subject_ty.is_single_valued(db) {
|
||||
let truthiness =
|
||||
|
||||
Reference in New Issue
Block a user