mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-29 03:02:27 +00:00
[ty] Reduce the overwhelming complexity of TypeInferenceBuilder::infer_call_expression (#18943)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary
This function is huge, and hugely indented. This PR breaks most of it
out into two helper functions: `KnownFunction::check_call()` and
`KnownClass::check_call`.
My immediate motivation is that we need to add yet more special cases to
this function in order to properly handle `tuple` instantiations and
instantiations of tuple subclasses. But I really don't relish the
thought of doing that with the function's current structure 😆
## Test Plan
Existing tests all pass. No new ones are added; this is a pure refactor
that should have no functional change.
This commit is contained in:
parent
c77e72ea1a
commit
4a5715b97a
4 changed files with 592 additions and 646 deletions
|
|
@ -9,14 +9,20 @@ use super::{
|
||||||
function::{FunctionDecorators, FunctionType},
|
function::{FunctionDecorators, FunctionType},
|
||||||
infer_expression_type, infer_unpack_types,
|
infer_expression_type, infer_unpack_types,
|
||||||
};
|
};
|
||||||
use crate::semantic_index::DeclarationWithConstraint;
|
|
||||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||||
|
use crate::semantic_index::place::NodeWithScopeKind;
|
||||||
|
use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex};
|
||||||
|
use crate::types::context::InferContext;
|
||||||
|
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
||||||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization};
|
||||||
|
use crate::types::infer::nearest_enclosing_class;
|
||||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||||
use crate::types::tuple::TupleType;
|
use crate::types::tuple::TupleType;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeRelation, TypeVarInstance,
|
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
|
||||||
|
KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
||||||
|
TypeVarInstance, TypeVarKind, infer_definition_types,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
Db, FxOrderSet, KnownModule, Program,
|
Db, FxOrderSet, KnownModule, Program,
|
||||||
|
|
@ -2330,7 +2336,7 @@ pub enum KnownClass {
|
||||||
NamedTupleFallback,
|
NamedTupleFallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> KnownClass {
|
impl KnownClass {
|
||||||
pub(crate) const fn is_bool(self) -> bool {
|
pub(crate) const fn is_bool(self) -> bool {
|
||||||
matches!(self, Self::Bool)
|
matches!(self, Self::Bool)
|
||||||
}
|
}
|
||||||
|
|
@ -2571,7 +2577,7 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'static str {
|
pub(crate) fn name(self, db: &dyn Db) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Any => "Any",
|
Self::Any => "Any",
|
||||||
Self::Bool => "bool",
|
Self::Bool => "bool",
|
||||||
|
|
@ -2649,7 +2655,7 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
pub(super) fn display(self, db: &dyn Db) -> impl std::fmt::Display + '_ {
|
||||||
struct KnownClassDisplay<'db> {
|
struct KnownClassDisplay<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
class: KnownClass,
|
class: KnownClass,
|
||||||
|
|
@ -2677,7 +2683,7 @@ impl<'db> KnownClass {
|
||||||
/// representing all possible instances of the class.
|
/// representing all possible instances of the class.
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn to_instance(self, db: &dyn Db) -> Type {
|
||||||
self.to_class_literal(db)
|
self.to_class_literal(db)
|
||||||
.to_class_type(db)
|
.to_class_type(db)
|
||||||
.map(|class| Type::instance(db, class))
|
.map(|class| Type::instance(db, class))
|
||||||
|
|
@ -2689,7 +2695,7 @@ impl<'db> KnownClass {
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
||||||
/// number of types, a debug-level log message will be emitted stating this.
|
/// number of types, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn to_specialized_class_type(
|
pub(crate) fn to_specialized_class_type<'db>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: impl IntoIterator<Item = Type<'db>>,
|
specialization: impl IntoIterator<Item = Type<'db>>,
|
||||||
|
|
@ -2722,7 +2728,7 @@ impl<'db> KnownClass {
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
||||||
/// number of types, a debug-level log message will be emitted stating this.
|
/// number of types, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn to_specialized_instance(
|
pub(crate) fn to_specialized_instance<'db>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: impl IntoIterator<Item = Type<'db>>,
|
specialization: impl IntoIterator<Item = Type<'db>>,
|
||||||
|
|
@ -2738,8 +2744,8 @@ impl<'db> KnownClass {
|
||||||
/// or if the symbol is not a class definition, or if the symbol is possibly unbound.
|
/// or if the symbol is not a class definition, or if the symbol is possibly unbound.
|
||||||
fn try_to_class_literal_without_logging(
|
fn try_to_class_literal_without_logging(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &dyn Db,
|
||||||
) -> Result<ClassLiteral<'db>, KnownClassLookupError<'db>> {
|
) -> Result<ClassLiteral, KnownClassLookupError> {
|
||||||
let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).place;
|
let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).place;
|
||||||
match symbol {
|
match symbol {
|
||||||
Place::Type(Type::ClassLiteral(class_literal), Boundness::Bound) => Ok(class_literal),
|
Place::Type(Type::ClassLiteral(class_literal), Boundness::Bound) => Ok(class_literal),
|
||||||
|
|
@ -2756,7 +2762,7 @@ impl<'db> KnownClass {
|
||||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn try_to_class_literal(self, db: &'db dyn Db) -> Option<ClassLiteral<'db>> {
|
pub(crate) fn try_to_class_literal(self, db: &dyn Db) -> Option<ClassLiteral> {
|
||||||
// a cache of the `KnownClass`es that we have already failed to lookup in typeshed
|
// a cache of the `KnownClass`es that we have already failed to lookup in typeshed
|
||||||
// (and therefore that we've already logged a warning for)
|
// (and therefore that we've already logged a warning for)
|
||||||
static MESSAGES: LazyLock<Mutex<FxHashSet<KnownClass>>> = LazyLock::new(Mutex::default);
|
static MESSAGES: LazyLock<Mutex<FxHashSet<KnownClass>>> = LazyLock::new(Mutex::default);
|
||||||
|
|
@ -2791,7 +2797,7 @@ impl<'db> KnownClass {
|
||||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn to_class_literal(self, db: &dyn Db) -> Type {
|
||||||
self.try_to_class_literal(db)
|
self.try_to_class_literal(db)
|
||||||
.map(Type::ClassLiteral)
|
.map(Type::ClassLiteral)
|
||||||
.unwrap_or_else(Type::unknown)
|
.unwrap_or_else(Type::unknown)
|
||||||
|
|
@ -2801,7 +2807,7 @@ impl<'db> KnownClass {
|
||||||
/// representing that class and all possible subclasses of the class.
|
/// representing that class and all possible subclasses of the class.
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn to_subclass_of(self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn to_subclass_of(self, db: &dyn Db) -> Type {
|
||||||
self.to_class_literal(db)
|
self.to_class_literal(db)
|
||||||
.to_class_type(db)
|
.to_class_type(db)
|
||||||
.map(|class| SubclassOfType::from(db, class))
|
.map(|class| SubclassOfType::from(db, class))
|
||||||
|
|
@ -2810,13 +2816,13 @@ impl<'db> KnownClass {
|
||||||
|
|
||||||
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
||||||
/// *and* `class` is a subclass of `other`.
|
/// *and* `class` is a subclass of `other`.
|
||||||
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_subclass_of<'db>(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||||
self.try_to_class_literal_without_logging(db)
|
self.try_to_class_literal_without_logging(db)
|
||||||
.is_ok_and(|class| class.is_subclass_of(db, None, other))
|
.is_ok_and(|class| class.is_subclass_of(db, None, other))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the module in which we should look up the definition for this class
|
/// Return the module in which we should look up the definition for this class
|
||||||
fn canonical_module(self, db: &'db dyn Db) -> KnownModule {
|
fn canonical_module(self, db: &dyn Db) -> KnownModule {
|
||||||
match self {
|
match self {
|
||||||
Self::Bool
|
Self::Bool
|
||||||
| Self::Object
|
| Self::Object
|
||||||
|
|
@ -3114,7 +3120,7 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the module of `self` matches `module`
|
/// Return `true` if the module of `self` matches `module`
|
||||||
fn check_module(self, db: &'db dyn Db, module: KnownModule) -> bool {
|
fn check_module(self, db: &dyn Db, module: KnownModule) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any
|
Self::Any
|
||||||
| Self::Bool
|
| Self::Bool
|
||||||
|
|
@ -3177,6 +3183,278 @@ impl<'db> KnownClass {
|
||||||
| Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions),
|
| Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate a call to this known class, and emit any diagnostics that are necessary
|
||||||
|
/// as a result of the call.
|
||||||
|
pub(super) fn check_call<'db>(
|
||||||
|
self,
|
||||||
|
context: &InferContext<'db, '_>,
|
||||||
|
index: &SemanticIndex<'db>,
|
||||||
|
overload_binding: &mut Binding<'db>,
|
||||||
|
call_argument_types: &CallArgumentTypes<'_, 'db>,
|
||||||
|
call_expression: &ast::ExprCall,
|
||||||
|
) {
|
||||||
|
let db = context.db();
|
||||||
|
let scope = context.scope();
|
||||||
|
let module = context.module();
|
||||||
|
|
||||||
|
match self {
|
||||||
|
KnownClass::Super => {
|
||||||
|
// Handle the case where `super()` is called with no arguments.
|
||||||
|
// In this case, we need to infer the two arguments:
|
||||||
|
// 1. The nearest enclosing class
|
||||||
|
// 2. The first parameter of the current function (typically `self` or `cls`)
|
||||||
|
match overload_binding.parameter_types() {
|
||||||
|
[] => {
|
||||||
|
let Some(enclosing_class) =
|
||||||
|
nearest_enclosing_class(db, index, scope, module)
|
||||||
|
else {
|
||||||
|
overload_binding.set_return_type(Type::unknown());
|
||||||
|
BoundSuperError::UnavailableImplicitArguments
|
||||||
|
.report_diagnostic(context, call_expression.into());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The type of the first parameter if the given scope is function-like (i.e. function or lambda).
|
||||||
|
// `None` if the scope is not function-like, or has no parameters.
|
||||||
|
let first_param = match scope.node(db) {
|
||||||
|
NodeWithScopeKind::Function(f) => {
|
||||||
|
f.node(module).parameters.iter().next()
|
||||||
|
}
|
||||||
|
NodeWithScopeKind::Lambda(l) => l
|
||||||
|
.node(module)
|
||||||
|
.parameters
|
||||||
|
.as_ref()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.next(),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(first_param) = first_param else {
|
||||||
|
overload_binding.set_return_type(Type::unknown());
|
||||||
|
BoundSuperError::UnavailableImplicitArguments
|
||||||
|
.report_diagnostic(context, call_expression.into());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let definition = index.expect_single_definition(first_param);
|
||||||
|
let first_param =
|
||||||
|
infer_definition_types(db, definition).binding_type(definition);
|
||||||
|
|
||||||
|
let bound_super = BoundSuperType::build(
|
||||||
|
db,
|
||||||
|
Type::ClassLiteral(enclosing_class),
|
||||||
|
first_param,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
err.report_diagnostic(context, call_expression.into());
|
||||||
|
Type::unknown()
|
||||||
|
});
|
||||||
|
|
||||||
|
overload_binding.set_return_type(bound_super);
|
||||||
|
}
|
||||||
|
[Some(pivot_class_type), Some(owner_type)] => {
|
||||||
|
let bound_super = BoundSuperType::build(db, *pivot_class_type, *owner_type)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
err.report_diagnostic(context, call_expression.into());
|
||||||
|
Type::unknown()
|
||||||
|
});
|
||||||
|
|
||||||
|
overload_binding.set_return_type(bound_super);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KnownClass::TypeVar => {
|
||||||
|
let assigned_to = index
|
||||||
|
.try_expression(ast::ExprRef::from(call_expression))
|
||||||
|
.and_then(|expr| expr.assigned_to(db));
|
||||||
|
|
||||||
|
let Some(target) = assigned_to.as_ref().and_then(|assigned_to| {
|
||||||
|
match assigned_to.node(module).targets.as_slice() {
|
||||||
|
[ast::Expr::Name(target)] => Some(target),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}) else {
|
||||||
|
if let Some(builder) =
|
||||||
|
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||||
|
{
|
||||||
|
builder.into_diagnostic(
|
||||||
|
"A legacy `typing.TypeVar` must be immediately assigned to a variable",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let [
|
||||||
|
Some(name_param),
|
||||||
|
constraints,
|
||||||
|
bound,
|
||||||
|
default,
|
||||||
|
contravariant,
|
||||||
|
covariant,
|
||||||
|
_infer_variance,
|
||||||
|
] = overload_binding.parameter_types()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let covariant = covariant
|
||||||
|
.map(|ty| ty.bool(db))
|
||||||
|
.unwrap_or(Truthiness::AlwaysFalse);
|
||||||
|
|
||||||
|
let contravariant = contravariant
|
||||||
|
.map(|ty| ty.bool(db))
|
||||||
|
.unwrap_or(Truthiness::AlwaysFalse);
|
||||||
|
|
||||||
|
let variance = match (contravariant, covariant) {
|
||||||
|
(Truthiness::Ambiguous, _) => {
|
||||||
|
let Some(builder) =
|
||||||
|
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
builder.into_diagnostic(
|
||||||
|
"The `contravariant` parameter of a legacy `typing.TypeVar` \
|
||||||
|
cannot have an ambiguous value",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(_, Truthiness::Ambiguous) => {
|
||||||
|
let Some(builder) =
|
||||||
|
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
builder.into_diagnostic(
|
||||||
|
"The `covariant` parameter of a legacy `typing.TypeVar` \
|
||||||
|
cannot have an ambiguous value",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(Truthiness::AlwaysTrue, Truthiness::AlwaysTrue) => {
|
||||||
|
let Some(builder) =
|
||||||
|
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
builder.into_diagnostic(
|
||||||
|
"A legacy `typing.TypeVar` cannot be both covariant and contravariant",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(Truthiness::AlwaysTrue, Truthiness::AlwaysFalse) => {
|
||||||
|
TypeVarVariance::Contravariant
|
||||||
|
}
|
||||||
|
(Truthiness::AlwaysFalse, Truthiness::AlwaysTrue) => TypeVarVariance::Covariant,
|
||||||
|
(Truthiness::AlwaysFalse, Truthiness::AlwaysFalse) => {
|
||||||
|
TypeVarVariance::Invariant
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let name_param = name_param.into_string_literal().map(|name| name.value(db));
|
||||||
|
|
||||||
|
if name_param.is_none_or(|name_param| name_param != target.id) {
|
||||||
|
let Some(builder) =
|
||||||
|
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
builder.into_diagnostic(format_args!(
|
||||||
|
"The name of a legacy `typing.TypeVar`{} must match \
|
||||||
|
the name of the variable it is assigned to (`{}`)",
|
||||||
|
if let Some(name_param) = name_param {
|
||||||
|
format!(" (`{name_param}`)")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
target.id,
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bound_or_constraint = match (bound, constraints) {
|
||||||
|
(Some(bound), None) => Some(TypeVarBoundOrConstraints::UpperBound(*bound)),
|
||||||
|
|
||||||
|
(None, Some(_constraints)) => {
|
||||||
|
// We don't use UnionType::from_elements or UnionBuilder here,
|
||||||
|
// because we don't want to simplify the list of constraints like
|
||||||
|
// we do with the elements of an actual union type.
|
||||||
|
// TODO: Consider using a new `OneOfType` connective here instead,
|
||||||
|
// since that more accurately represents the actual semantics of
|
||||||
|
// typevar constraints.
|
||||||
|
let elements = UnionType::new(
|
||||||
|
db,
|
||||||
|
overload_binding
|
||||||
|
.arguments_for_parameter(call_argument_types, 1)
|
||||||
|
.map(|(_, ty)| ty)
|
||||||
|
.collect::<Box<_>>(),
|
||||||
|
);
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Emit a diagnostic that TypeVar cannot be both bounded and
|
||||||
|
// constrained
|
||||||
|
(Some(_), Some(_)) => return,
|
||||||
|
|
||||||
|
(None, None) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let containing_assignment = index.expect_single_definition(target);
|
||||||
|
overload_binding.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar(
|
||||||
|
TypeVarInstance::new(
|
||||||
|
db,
|
||||||
|
target.id.clone(),
|
||||||
|
Some(containing_assignment),
|
||||||
|
bound_or_constraint,
|
||||||
|
variance,
|
||||||
|
*default,
|
||||||
|
TypeVarKind::Legacy,
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
KnownClass::TypeAliasType => {
|
||||||
|
let assigned_to = index
|
||||||
|
.try_expression(ast::ExprRef::from(call_expression))
|
||||||
|
.and_then(|expr| expr.assigned_to(db));
|
||||||
|
|
||||||
|
let containing_assignment = assigned_to.as_ref().and_then(|assigned_to| {
|
||||||
|
match assigned_to.node(module).targets.as_slice() {
|
||||||
|
[ast::Expr::Name(target)] => Some(index.expect_single_definition(target)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let [Some(name), Some(value), ..] = overload_binding.parameter_types() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(name) = name.into_string_literal() {
|
||||||
|
overload_binding.set_return_type(Type::KnownInstance(
|
||||||
|
KnownInstanceType::TypeAliasType(TypeAliasType::Bare(
|
||||||
|
BareTypeAliasType::new(
|
||||||
|
db,
|
||||||
|
ast::name::Name::new(name.value(db)),
|
||||||
|
containing_assignment,
|
||||||
|
value,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
} else if let Some(builder) =
|
||||||
|
context.report_lint(&INVALID_TYPE_ALIAS_TYPE, call_expression)
|
||||||
|
{
|
||||||
|
builder.into_diagnostic(
|
||||||
|
"The name of a `typing.TypeAlias` must be a string literal",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumeration of ways in which looking up a [`KnownClass`] in typeshed could fail.
|
/// Enumeration of ways in which looking up a [`KnownClass`] in typeshed could fail.
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||||
self.module
|
self.module
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn scope(&self) -> ScopeId<'db> {
|
||||||
|
self.scope
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a span with the range of the given expression
|
/// Create a span with the range of the given expression
|
||||||
/// in the file being currently type checked.
|
/// in the file being currently type checked.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use ruff_db::diagnostic::Span;
|
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span};
|
||||||
use ruff_db::files::{File, FileRange};
|
use ruff_db::files::{File, FileRange};
|
||||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
|
@ -64,11 +64,18 @@ use crate::semantic_index::ast_ids::HasScopedUseId;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::place::ScopeId;
|
use crate::semantic_index::place::ScopeId;
|
||||||
use crate::semantic_index::semantic_index;
|
use crate::semantic_index::semantic_index;
|
||||||
|
use crate::types::context::InferContext;
|
||||||
|
use crate::types::diagnostic::{
|
||||||
|
REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE,
|
||||||
|
report_bad_argument_to_get_protocol_members,
|
||||||
|
report_runtime_check_against_non_runtime_checkable_protocol,
|
||||||
|
};
|
||||||
use crate::types::generics::GenericContext;
|
use crate::types::generics::GenericContext;
|
||||||
use crate::types::narrow::ClassInfoConstraintFunction;
|
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||||
use crate::types::signatures::{CallableSignature, Signature};
|
use crate::types::signatures::{CallableSignature, Signature};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BoundMethodType, CallableType, Type, TypeMapping, TypeRelation, TypeVarInstance,
|
Binding, BoundMethodType, CallableType, DynamicType, Type, TypeMapping, TypeRelation,
|
||||||
|
TypeVarInstance,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
|
@ -942,6 +949,199 @@ impl KnownFunction {
|
||||||
| Self::AllMembers => module.is_ty_extensions(),
|
| Self::AllMembers => module.is_ty_extensions(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate a call to this known function, and emit any diagnostics that are necessary
|
||||||
|
/// as a result of the call.
|
||||||
|
pub(super) fn check_call(
|
||||||
|
self,
|
||||||
|
context: &InferContext,
|
||||||
|
overload_binding: &mut Binding,
|
||||||
|
call_expression: &ast::ExprCall,
|
||||||
|
) {
|
||||||
|
let db = context.db();
|
||||||
|
|
||||||
|
match self {
|
||||||
|
KnownFunction::RevealType => {
|
||||||
|
let [Some(revealed_type)] = overload_binding.parameter_types() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(builder) =
|
||||||
|
context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut diag = builder.into_diagnostic("Revealed type");
|
||||||
|
let span = context.span(&call_expression.arguments.args[0]);
|
||||||
|
diag.annotate(
|
||||||
|
Annotation::primary(span)
|
||||||
|
.message(format_args!("`{}`", revealed_type.display(db))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
KnownFunction::AssertType => {
|
||||||
|
let [Some(actual_ty), Some(asserted_ty)] = overload_binding.parameter_types()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if actual_ty.is_equivalent_to(db, *asserted_ty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(builder) = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||||
|
"Argument does not have asserted type `{}`",
|
||||||
|
asserted_ty.display(db),
|
||||||
|
));
|
||||||
|
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(context.span(&call_expression.arguments.args[0]))
|
||||||
|
.message(format_args!(
|
||||||
|
"Inferred type of argument is `{}`",
|
||||||
|
actual_ty.display(db),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"`{asserted_type}` and `{inferred_type}` are not equivalent types",
|
||||||
|
asserted_type = asserted_ty.display(db),
|
||||||
|
inferred_type = actual_ty.display(db),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
KnownFunction::AssertNever => {
|
||||||
|
let [Some(actual_ty)] = overload_binding.parameter_types() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if actual_ty.is_equivalent_to(db, Type::Never) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(builder) = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diagnostic =
|
||||||
|
builder.into_diagnostic("Argument does not have asserted type `Never`");
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(context.span(&call_expression.arguments.args[0]))
|
||||||
|
.message(format_args!(
|
||||||
|
"Inferred type of argument is `{}`",
|
||||||
|
actual_ty.display(db)
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"`Never` and `{inferred_type}` are not equivalent types",
|
||||||
|
inferred_type = actual_ty.display(db),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
KnownFunction::StaticAssert => {
|
||||||
|
let [Some(parameter_ty), message] = overload_binding.parameter_types() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let truthiness = match parameter_ty.try_bool(db) {
|
||||||
|
Ok(truthiness) => truthiness,
|
||||||
|
Err(err) => {
|
||||||
|
let condition = call_expression
|
||||||
|
.arguments
|
||||||
|
.find_argument("condition", 0)
|
||||||
|
.map(|argument| match argument {
|
||||||
|
ruff_python_ast::ArgOrKeyword::Arg(expr) => {
|
||||||
|
ast::AnyNodeRef::from(expr)
|
||||||
|
}
|
||||||
|
ruff_python_ast::ArgOrKeyword::Keyword(keyword) => {
|
||||||
|
ast::AnyNodeRef::from(keyword)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(ast::AnyNodeRef::from(call_expression));
|
||||||
|
|
||||||
|
err.report_diagnostic(context, condition);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(builder) = context.report_lint(&STATIC_ASSERT_ERROR, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if truthiness.is_always_true() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(message) = message
|
||||||
|
.and_then(Type::into_string_literal)
|
||||||
|
.map(|s| s.value(db))
|
||||||
|
{
|
||||||
|
builder.into_diagnostic(format_args!("Static assertion error: {message}"));
|
||||||
|
} else if *parameter_ty == Type::BooleanLiteral(false) {
|
||||||
|
builder
|
||||||
|
.into_diagnostic("Static assertion error: argument evaluates to `False`");
|
||||||
|
} else if truthiness.is_always_false() {
|
||||||
|
builder.into_diagnostic(format_args!(
|
||||||
|
"Static assertion error: argument of type `{parameter_ty}` \
|
||||||
|
is statically known to be falsy",
|
||||||
|
parameter_ty = parameter_ty.display(db)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
builder.into_diagnostic(format_args!(
|
||||||
|
"Static assertion error: argument of type `{parameter_ty}` \
|
||||||
|
has an ambiguous static truthiness",
|
||||||
|
parameter_ty = parameter_ty.display(db)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KnownFunction::Cast => {
|
||||||
|
let [Some(casted_type), Some(source_type)] = overload_binding.parameter_types()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let contains_unknown_or_todo =
|
||||||
|
|ty| matches!(ty, Type::Dynamic(dynamic) if dynamic != DynamicType::Any);
|
||||||
|
if source_type.is_equivalent_to(db, *casted_type)
|
||||||
|
&& !casted_type.any_over_type(db, &|ty| contains_unknown_or_todo(ty))
|
||||||
|
&& !source_type.any_over_type(db, &|ty| contains_unknown_or_todo(ty))
|
||||||
|
{
|
||||||
|
let Some(builder) = context.report_lint(&REDUNDANT_CAST, call_expression)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
builder.into_diagnostic(format_args!(
|
||||||
|
"Value is already of type `{}`",
|
||||||
|
casted_type.display(db),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KnownFunction::GetProtocolMembers => {
|
||||||
|
let [Some(Type::ClassLiteral(class))] = overload_binding.parameter_types() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if class.is_protocol(db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
report_bad_argument_to_get_protocol_members(context, call_expression, *class);
|
||||||
|
}
|
||||||
|
KnownFunction::IsInstance | KnownFunction::IsSubclass => {
|
||||||
|
let [_, Some(Type::ClassLiteral(class))] = overload_binding.parameter_types()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(protocol_class) = class.into_protocol_class(db) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if protocol_class.is_runtime_checkable(db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
report_runtime_check_against_non_runtime_checkable_protocol(
|
||||||
|
context,
|
||||||
|
call_expression,
|
||||||
|
protocol_class,
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@
|
||||||
//! be considered a bug.)
|
//! be considered a bug.)
|
||||||
|
|
||||||
use itertools::{Either, Itertools};
|
use itertools::{Either, Itertools};
|
||||||
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity};
|
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||||
use ruff_python_ast::visitor::{Visitor, walk_expr};
|
use ruff_python_ast::visitor::{Visitor, walk_expr};
|
||||||
|
|
@ -79,16 +78,15 @@ use crate::types::diagnostic::{
|
||||||
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
||||||
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO,
|
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO,
|
||||||
INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
|
INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
|
||||||
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE,
|
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
|
||||||
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
|
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases,
|
||||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, POSSIBLY_UNBOUND_IMPLICIT_CALL,
|
POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics,
|
||||||
POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE,
|
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE,
|
||||||
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type,
|
UNSUPPORTED_OPERATOR, report_implicit_return_type, report_instance_layout_conflict,
|
||||||
report_instance_layout_conflict, report_invalid_argument_number_to_special_form,
|
report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated,
|
||||||
report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable,
|
report_invalid_arguments_to_callable, report_invalid_assignment,
|
||||||
report_invalid_assignment, report_invalid_attribute_assignment,
|
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
||||||
report_invalid_generator_function_return_type, report_invalid_return_type,
|
report_invalid_return_type, report_possibly_unbound_attribute,
|
||||||
report_possibly_unbound_attribute,
|
|
||||||
};
|
};
|
||||||
use crate::types::function::{
|
use crate::types::function::{
|
||||||
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
|
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
|
||||||
|
|
@ -99,11 +97,11 @@ use crate::types::signatures::{CallableSignature, Signature};
|
||||||
use crate::types::tuple::{TupleSpec, TupleType};
|
use crate::types::tuple::{TupleSpec, TupleType};
|
||||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams,
|
CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType,
|
||||||
DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType,
|
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard,
|
||||||
LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
|
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
|
||||||
ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Truthiness,
|
Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Truthiness, Type,
|
||||||
Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeIsType, TypeQualifiers,
|
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeIsType, TypeQualifiers,
|
||||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder,
|
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder,
|
||||||
UnionType, binding_type, todo_type,
|
UnionType, binding_type, todo_type,
|
||||||
};
|
};
|
||||||
|
|
@ -113,25 +111,19 @@ use crate::{Db, FxOrderSet, Program};
|
||||||
|
|
||||||
use super::context::{InNoTypeCheck, InferContext};
|
use super::context::{InNoTypeCheck, InferContext};
|
||||||
use super::diagnostic::{
|
use super::diagnostic::{
|
||||||
INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, REDUNDANT_CAST, STATIC_ASSERT_ERROR,
|
INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, SUBCLASS_OF_FINAL_CLASS,
|
||||||
SUBCLASS_OF_FINAL_CLASS, TYPE_ASSERTION_FAILURE,
|
|
||||||
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
|
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
|
||||||
report_bad_argument_to_get_protocol_members, report_duplicate_bases,
|
report_duplicate_bases, report_index_out_of_bounds, report_invalid_exception_caught,
|
||||||
report_index_out_of_bounds, report_invalid_exception_caught, report_invalid_exception_cause,
|
report_invalid_exception_cause, report_invalid_exception_raised,
|
||||||
report_invalid_exception_raised, report_invalid_or_unsupported_base,
|
report_invalid_or_unsupported_base, report_invalid_type_checking_constant,
|
||||||
report_invalid_type_checking_constant, report_non_subscriptable,
|
report_non_subscriptable, report_possibly_unresolved_reference, report_slice_step_size_zero,
|
||||||
report_possibly_unresolved_reference,
|
|
||||||
report_runtime_check_against_non_runtime_checkable_protocol, report_slice_step_size_zero,
|
|
||||||
};
|
};
|
||||||
use super::generics::LegacyGenericBase;
|
use super::generics::LegacyGenericBase;
|
||||||
use super::string_annotation::{
|
use super::string_annotation::{
|
||||||
BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation,
|
BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation,
|
||||||
};
|
};
|
||||||
use super::subclass_of::SubclassOfInner;
|
use super::subclass_of::SubclassOfInner;
|
||||||
use super::{
|
use super::{ClassBase, NominalInstanceType, add_inferred_python_version_hint_to_diagnostic};
|
||||||
BoundSuperError, BoundSuperType, ClassBase, NominalInstanceType,
|
|
||||||
add_inferred_python_version_hint_to_diagnostic,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
|
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
|
||||||
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
|
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
|
||||||
|
|
@ -4682,9 +4674,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
ast::Expr::Named(named) => self.infer_named_expression(named),
|
ast::Expr::Named(named) => self.infer_named_expression(named),
|
||||||
ast::Expr::If(if_expression) => self.infer_if_expression(if_expression),
|
ast::Expr::If(if_expression) => self.infer_if_expression(if_expression),
|
||||||
ast::Expr::Lambda(lambda_expression) => self.infer_lambda_expression(lambda_expression),
|
ast::Expr::Lambda(lambda_expression) => self.infer_lambda_expression(lambda_expression),
|
||||||
ast::Expr::Call(call_expression) => {
|
ast::Expr::Call(call_expression) => self.infer_call_expression(call_expression),
|
||||||
self.infer_call_expression(expression, call_expression)
|
|
||||||
}
|
|
||||||
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
|
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
|
||||||
ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression),
|
ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression),
|
||||||
ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from),
|
ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from),
|
||||||
|
|
@ -5292,27 +5282,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
CallableType::function_like(self.db(), Signature::new(parameters, Some(Type::unknown())))
|
CallableType::function_like(self.db(), Signature::new(parameters, Some(Type::unknown())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the type of the first parameter if the given scope is function-like (i.e. function or lambda).
|
fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> {
|
||||||
/// Returns `None` if the scope is not function-like, or has no parameters.
|
|
||||||
fn first_param_type_in_scope(&self, scope: ScopeId) -> Option<Type<'db>> {
|
|
||||||
let first_param = match scope.node(self.db()) {
|
|
||||||
NodeWithScopeKind::Function(f) => f.node(self.module()).parameters.iter().next(),
|
|
||||||
NodeWithScopeKind::Lambda(l) => {
|
|
||||||
l.node(self.module()).parameters.as_ref()?.iter().next()
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let definition = self.index.expect_single_definition(first_param);
|
|
||||||
|
|
||||||
Some(infer_definition_types(self.db(), definition).binding_type(definition))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn infer_call_expression(
|
|
||||||
&mut self,
|
|
||||||
call_expression_node: &ast::Expr,
|
|
||||||
call_expression: &ast::ExprCall,
|
|
||||||
) -> Type<'db> {
|
|
||||||
let ast::ExprCall {
|
let ast::ExprCall {
|
||||||
range: _,
|
range: _,
|
||||||
node_index: _,
|
node_index: _,
|
||||||
|
|
@ -5412,230 +5382,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let call_argument_types =
|
let call_argument_types =
|
||||||
self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms);
|
self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms);
|
||||||
|
|
||||||
match bindings.check_types(self.db(), &call_argument_types) {
|
let mut bindings = match bindings.check_types(self.db(), &call_argument_types) {
|
||||||
Ok(mut bindings) => {
|
Ok(bindings) => bindings,
|
||||||
|
Err(CallError(_, bindings)) => {
|
||||||
|
bindings.report_diagnostics(&self.context, call_expression.into());
|
||||||
|
return bindings.return_type(self.db());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for binding in &mut bindings {
|
for binding in &mut bindings {
|
||||||
let binding_type = binding.callable_type;
|
let binding_type = binding.callable_type;
|
||||||
for (_, overload) in binding.matching_overloads_mut() {
|
for (_, overload) in binding.matching_overloads_mut() {
|
||||||
match binding_type {
|
match binding_type {
|
||||||
Type::FunctionLiteral(function_literal) => {
|
Type::FunctionLiteral(function_literal) => {
|
||||||
let Some(known_function) = function_literal.known(self.db()) else {
|
if let Some(known_function) = function_literal.known(self.db()) {
|
||||||
continue;
|
known_function.check_call(&self.context, overload, call_expression);
|
||||||
};
|
|
||||||
|
|
||||||
match known_function {
|
|
||||||
KnownFunction::RevealType => {
|
|
||||||
if let [Some(revealed_type)] = overload.parameter_types() {
|
|
||||||
if let Some(builder) = self.context.report_diagnostic(
|
|
||||||
DiagnosticId::RevealedType,
|
|
||||||
Severity::Info,
|
|
||||||
) {
|
|
||||||
let mut diag =
|
|
||||||
builder.into_diagnostic("Revealed type");
|
|
||||||
let span = self
|
|
||||||
.context
|
|
||||||
.span(&call_expression.arguments.args[0]);
|
|
||||||
diag.annotate(Annotation::primary(span).message(
|
|
||||||
format_args!(
|
|
||||||
"`{}`",
|
|
||||||
revealed_type.display(self.db())
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KnownFunction::AssertType => {
|
|
||||||
if let [Some(actual_ty), Some(asserted_ty)] =
|
|
||||||
overload.parameter_types()
|
|
||||||
{
|
|
||||||
if !actual_ty.is_equivalent_to(self.db(), *asserted_ty)
|
|
||||||
{
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&TYPE_ASSERTION_FAILURE,
|
|
||||||
call_expression,
|
|
||||||
) {
|
|
||||||
let mut diagnostic =
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"Argument does not have asserted type `{}`",
|
|
||||||
asserted_ty.display(self.db()),
|
|
||||||
));
|
|
||||||
diagnostic.annotate(
|
|
||||||
Annotation::secondary(self.context.span(
|
|
||||||
&call_expression.arguments.args[0],
|
|
||||||
))
|
|
||||||
.message(format_args!(
|
|
||||||
"Inferred type of argument is `{}`",
|
|
||||||
actual_ty.display(self.db()),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
diagnostic.info(
|
|
||||||
format_args!(
|
|
||||||
"`{asserted_type}` and `{inferred_type}` are not equivalent types",
|
|
||||||
asserted_type = asserted_ty.display(self.db()),
|
|
||||||
inferred_type = actual_ty.display(self.db()),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KnownFunction::AssertNever => {
|
|
||||||
if let [Some(actual_ty)] = overload.parameter_types() {
|
|
||||||
if !actual_ty.is_equivalent_to(self.db(), Type::Never) {
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&TYPE_ASSERTION_FAILURE,
|
|
||||||
call_expression,
|
|
||||||
) {
|
|
||||||
let mut diagnostic = builder.into_diagnostic(
|
|
||||||
"Argument does not have asserted type `Never`",
|
|
||||||
);
|
|
||||||
diagnostic.annotate(
|
|
||||||
Annotation::secondary(self.context.span(
|
|
||||||
&call_expression.arguments.args[0],
|
|
||||||
))
|
|
||||||
.message(format_args!(
|
|
||||||
"Inferred type of argument is `{}`",
|
|
||||||
actual_ty.display(self.db())
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
diagnostic.info(
|
|
||||||
format_args!(
|
|
||||||
"`Never` and `{inferred_type}` are not equivalent types",
|
|
||||||
inferred_type = actual_ty.display(self.db()),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KnownFunction::StaticAssert => {
|
|
||||||
if let [Some(parameter_ty), message] =
|
|
||||||
overload.parameter_types()
|
|
||||||
{
|
|
||||||
let truthiness = match parameter_ty.try_bool(self.db())
|
|
||||||
{
|
|
||||||
Ok(truthiness) => truthiness,
|
|
||||||
Err(err) => {
|
|
||||||
let condition = arguments
|
|
||||||
.find_argument("condition", 0)
|
|
||||||
.map(|argument| {
|
|
||||||
match argument {
|
|
||||||
ruff_python_ast::ArgOrKeyword::Arg(
|
|
||||||
expr,
|
|
||||||
) => ast::AnyNodeRef::from(expr),
|
|
||||||
ruff_python_ast::ArgOrKeyword::Keyword(
|
|
||||||
keyword,
|
|
||||||
) => ast::AnyNodeRef::from(keyword),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(ast::AnyNodeRef::from(
|
|
||||||
call_expression,
|
|
||||||
));
|
|
||||||
|
|
||||||
err.report_diagnostic(&self.context, condition);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(builder) = self
|
|
||||||
.context
|
|
||||||
.report_lint(&STATIC_ASSERT_ERROR, call_expression)
|
|
||||||
{
|
|
||||||
if !truthiness.is_always_true() {
|
|
||||||
if let Some(message) = message
|
|
||||||
.and_then(Type::into_string_literal)
|
|
||||||
.map(|s| s.value(self.db()))
|
|
||||||
{
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"Static assertion error: {message}"
|
|
||||||
));
|
|
||||||
} else if *parameter_ty
|
|
||||||
== Type::BooleanLiteral(false)
|
|
||||||
{
|
|
||||||
builder.into_diagnostic(
|
|
||||||
"Static assertion error: \
|
|
||||||
argument evaluates to `False`",
|
|
||||||
);
|
|
||||||
} else if truthiness.is_always_false() {
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"Static assertion error: \
|
|
||||||
argument of type `{parameter_ty}` \
|
|
||||||
is statically known to be falsy",
|
|
||||||
parameter_ty =
|
|
||||||
parameter_ty.display(self.db())
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"Static assertion error: \
|
|
||||||
argument of type `{parameter_ty}` \
|
|
||||||
has an ambiguous static truthiness",
|
|
||||||
parameter_ty =
|
|
||||||
parameter_ty.display(self.db())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KnownFunction::Cast => {
|
|
||||||
if let [Some(casted_type), Some(source_type)] =
|
|
||||||
overload.parameter_types()
|
|
||||||
{
|
|
||||||
let db = self.db();
|
|
||||||
let contains_unknown_or_todo = |ty| matches!(ty, Type::Dynamic(dynamic) if dynamic != DynamicType::Any);
|
|
||||||
if source_type.is_equivalent_to(db, *casted_type)
|
|
||||||
&& !casted_type.any_over_type(db, &|ty| {
|
|
||||||
contains_unknown_or_todo(ty)
|
|
||||||
})
|
|
||||||
&& !source_type.any_over_type(db, &|ty| {
|
|
||||||
contains_unknown_or_todo(ty)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
if let Some(builder) = self
|
|
||||||
.context
|
|
||||||
.report_lint(&REDUNDANT_CAST, call_expression)
|
|
||||||
{
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"Value is already of type `{}`",
|
|
||||||
casted_type.display(db),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KnownFunction::GetProtocolMembers => {
|
|
||||||
if let [Some(Type::ClassLiteral(class))] =
|
|
||||||
overload.parameter_types()
|
|
||||||
{
|
|
||||||
if !class.is_protocol(self.db()) {
|
|
||||||
report_bad_argument_to_get_protocol_members(
|
|
||||||
&self.context,
|
|
||||||
call_expression,
|
|
||||||
*class,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KnownFunction::IsInstance | KnownFunction::IsSubclass => {
|
|
||||||
if let [_, Some(Type::ClassLiteral(class))] =
|
|
||||||
overload.parameter_types()
|
|
||||||
{
|
|
||||||
if let Some(protocol_class) =
|
|
||||||
class.into_protocol_class(self.db())
|
|
||||||
{
|
|
||||||
if !protocol_class.is_runtime_checkable(self.db()) {
|
|
||||||
report_runtime_check_against_non_runtime_checkable_protocol(
|
|
||||||
&self.context,
|
|
||||||
call_expression,
|
|
||||||
protocol_class,
|
|
||||||
known_function
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5643,305 +5404,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let Some(known_class) = class.known(self.db()) else {
|
let Some(known_class) = class.known(self.db()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
known_class.check_call(
|
||||||
match known_class {
|
&self.context,
|
||||||
KnownClass::Super => {
|
|
||||||
// Handle the case where `super()` is called with no arguments.
|
|
||||||
// In this case, we need to infer the two arguments:
|
|
||||||
// 1. The nearest enclosing class
|
|
||||||
// 2. The first parameter of the current function (typically `self` or `cls`)
|
|
||||||
match overload.parameter_types() {
|
|
||||||
[] => {
|
|
||||||
let scope = self.scope();
|
|
||||||
|
|
||||||
let Some(enclosing_class) = nearest_enclosing_class(
|
|
||||||
self.db(),
|
|
||||||
self.index,
|
self.index,
|
||||||
scope,
|
overload,
|
||||||
self.module(),
|
|
||||||
) else {
|
|
||||||
overload.set_return_type(Type::unknown());
|
|
||||||
BoundSuperError::UnavailableImplicitArguments
|
|
||||||
.report_diagnostic(
|
|
||||||
&self.context,
|
|
||||||
call_expression.into(),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(first_param) =
|
|
||||||
self.first_param_type_in_scope(scope)
|
|
||||||
else {
|
|
||||||
overload.set_return_type(Type::unknown());
|
|
||||||
BoundSuperError::UnavailableImplicitArguments
|
|
||||||
.report_diagnostic(
|
|
||||||
&self.context,
|
|
||||||
call_expression.into(),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let bound_super = BoundSuperType::build(
|
|
||||||
self.db(),
|
|
||||||
Type::ClassLiteral(enclosing_class),
|
|
||||||
first_param,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
err.report_diagnostic(
|
|
||||||
&self.context,
|
|
||||||
call_expression.into(),
|
|
||||||
);
|
|
||||||
Type::unknown()
|
|
||||||
});
|
|
||||||
|
|
||||||
overload.set_return_type(bound_super);
|
|
||||||
}
|
|
||||||
[Some(pivot_class_type), Some(owner_type)] => {
|
|
||||||
let bound_super = BoundSuperType::build(
|
|
||||||
self.db(),
|
|
||||||
*pivot_class_type,
|
|
||||||
*owner_type,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
err.report_diagnostic(
|
|
||||||
&self.context,
|
|
||||||
call_expression.into(),
|
|
||||||
);
|
|
||||||
Type::unknown()
|
|
||||||
});
|
|
||||||
|
|
||||||
overload.set_return_type(bound_super);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KnownClass::TypeVar => {
|
|
||||||
let assigned_to = (self.index)
|
|
||||||
.try_expression(call_expression_node)
|
|
||||||
.and_then(|expr| expr.assigned_to(self.db()));
|
|
||||||
|
|
||||||
let Some(target) =
|
|
||||||
assigned_to.as_ref().and_then(|assigned_to| {
|
|
||||||
match assigned_to
|
|
||||||
.node(self.module())
|
|
||||||
.targets
|
|
||||||
.as_slice()
|
|
||||||
{
|
|
||||||
[ast::Expr::Name(target)] => Some(target),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else {
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&INVALID_LEGACY_TYPE_VARIABLE,
|
|
||||||
call_expression,
|
|
||||||
) {
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"A legacy `typing.TypeVar` must be immediately assigned to a variable",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let [
|
|
||||||
Some(name_param),
|
|
||||||
constraints,
|
|
||||||
bound,
|
|
||||||
default,
|
|
||||||
contravariant,
|
|
||||||
covariant,
|
|
||||||
_infer_variance,
|
|
||||||
] = overload.parameter_types()
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let covariant = match covariant {
|
|
||||||
Some(ty) => ty.bool(self.db()),
|
|
||||||
None => Truthiness::AlwaysFalse,
|
|
||||||
};
|
|
||||||
|
|
||||||
let contravariant = match contravariant {
|
|
||||||
Some(ty) => ty.bool(self.db()),
|
|
||||||
None => Truthiness::AlwaysFalse,
|
|
||||||
};
|
|
||||||
|
|
||||||
let variance = match (contravariant, covariant) {
|
|
||||||
(Truthiness::Ambiguous, _) => {
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&INVALID_LEGACY_TYPE_VARIABLE,
|
|
||||||
call_expression,
|
|
||||||
) {
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"The `contravariant` parameter of \
|
|
||||||
a legacy `typing.TypeVar` cannot have \
|
|
||||||
an ambiguous value",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
(_, Truthiness::Ambiguous) => {
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&INVALID_LEGACY_TYPE_VARIABLE,
|
|
||||||
call_expression,
|
|
||||||
) {
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"The `covariant` parameter of \
|
|
||||||
a legacy `typing.TypeVar` cannot have \
|
|
||||||
an ambiguous value",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
(Truthiness::AlwaysTrue, Truthiness::AlwaysTrue) => {
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&INVALID_LEGACY_TYPE_VARIABLE,
|
|
||||||
call_expression,
|
|
||||||
) {
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"A legacy `typing.TypeVar` cannot be \
|
|
||||||
both covariant and contravariant",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
(Truthiness::AlwaysTrue, Truthiness::AlwaysFalse) => {
|
|
||||||
TypeVarVariance::Contravariant
|
|
||||||
}
|
|
||||||
(Truthiness::AlwaysFalse, Truthiness::AlwaysTrue) => {
|
|
||||||
TypeVarVariance::Covariant
|
|
||||||
}
|
|
||||||
(Truthiness::AlwaysFalse, Truthiness::AlwaysFalse) => {
|
|
||||||
TypeVarVariance::Invariant
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let name_param = name_param
|
|
||||||
.into_string_literal()
|
|
||||||
.map(|name| name.value(self.db()));
|
|
||||||
if name_param
|
|
||||||
.is_none_or(|name_param| name_param != target.id)
|
|
||||||
{
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&INVALID_LEGACY_TYPE_VARIABLE,
|
|
||||||
call_expression,
|
|
||||||
) {
|
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"The name of a legacy `typing.TypeVar`{} must match \
|
|
||||||
the name of the variable it is assigned to (`{}`)",
|
|
||||||
if let Some(name_param) = name_param {
|
|
||||||
format!(" (`{name_param}`)")
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
},
|
|
||||||
target.id,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bound_or_constraint = match (bound, constraints) {
|
|
||||||
(Some(bound), None) => {
|
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(*bound))
|
|
||||||
}
|
|
||||||
|
|
||||||
(None, Some(_constraints)) => {
|
|
||||||
// We don't use UnionType::from_elements or UnionBuilder here,
|
|
||||||
// because we don't want to simplify the list of constraints like
|
|
||||||
// we do with the elements of an actual union type.
|
|
||||||
// TODO: Consider using a new `OneOfType` connective here instead,
|
|
||||||
// since that more accurately represents the actual semantics of
|
|
||||||
// typevar constraints.
|
|
||||||
let elements = UnionType::new(
|
|
||||||
self.db(),
|
|
||||||
overload
|
|
||||||
.arguments_for_parameter(
|
|
||||||
&call_argument_types,
|
&call_argument_types,
|
||||||
1,
|
|
||||||
)
|
|
||||||
.map(|(_, ty)| ty)
|
|
||||||
.collect::<Box<_>>(),
|
|
||||||
);
|
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(
|
|
||||||
elements,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Emit a diagnostic that TypeVar cannot be both bounded and
|
|
||||||
// constrained
|
|
||||||
(Some(_), Some(_)) => continue,
|
|
||||||
|
|
||||||
(None, None) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let containing_assignment =
|
|
||||||
self.index.expect_single_definition(target);
|
|
||||||
overload.set_return_type(Type::KnownInstance(
|
|
||||||
KnownInstanceType::TypeVar(TypeVarInstance::new(
|
|
||||||
self.db(),
|
|
||||||
target.id.clone(),
|
|
||||||
Some(containing_assignment),
|
|
||||||
bound_or_constraint,
|
|
||||||
variance,
|
|
||||||
*default,
|
|
||||||
TypeVarKind::Legacy,
|
|
||||||
)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
KnownClass::TypeAliasType => {
|
|
||||||
let assigned_to = (self.index)
|
|
||||||
.try_expression(call_expression_node)
|
|
||||||
.and_then(|expr| expr.assigned_to(self.db()));
|
|
||||||
|
|
||||||
let containing_assignment =
|
|
||||||
assigned_to.as_ref().and_then(|assigned_to| {
|
|
||||||
match assigned_to
|
|
||||||
.node(self.module())
|
|
||||||
.targets
|
|
||||||
.as_slice()
|
|
||||||
{
|
|
||||||
[ast::Expr::Name(target)] => Some(
|
|
||||||
self.index.expect_single_definition(target),
|
|
||||||
),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let [Some(name), Some(value), ..] =
|
|
||||||
overload.parameter_types()
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(name) = name.into_string_literal() {
|
|
||||||
overload.set_return_type(Type::KnownInstance(
|
|
||||||
KnownInstanceType::TypeAliasType(
|
|
||||||
TypeAliasType::Bare(BareTypeAliasType::new(
|
|
||||||
self.db(),
|
|
||||||
ast::name::Name::new(name.value(self.db())),
|
|
||||||
containing_assignment,
|
|
||||||
value,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
if let Some(builder) = self.context.report_lint(
|
|
||||||
&INVALID_TYPE_ALIAS_TYPE,
|
|
||||||
call_expression,
|
call_expression,
|
||||||
) {
|
);
|
||||||
builder.into_diagnostic(format_args!(
|
|
||||||
"The name of a `typing.TypeAlias` must be a string literal",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
_ => {}
|
||||||
}
|
|
||||||
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5986,13 +5457,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(CallError(_, bindings)) => {
|
|
||||||
bindings.report_diagnostics(&self.context, call_expression.into());
|
|
||||||
bindings.return_type(self.db())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
|
fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
|
||||||
let ast::ExprStarred {
|
let ast::ExprStarred {
|
||||||
range: _,
|
range: _,
|
||||||
|
|
@ -9177,7 +8641,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::Expr::Call(call_expr) => {
|
ast::Expr::Call(call_expr) => {
|
||||||
self.infer_call_expression(expression, call_expr);
|
self.infer_call_expression(call_expr);
|
||||||
self.report_invalid_type_expression(
|
self.report_invalid_type_expression(
|
||||||
expression,
|
expression,
|
||||||
format_args!("Function calls are not allowed in type expressions"),
|
format_args!("Function calls are not allowed in type expressions"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue