[red-knot] Add support for typing.ClassVar (#15550)

## Summary

Add support for `typing.ClassVar`, i.e. emit a diagnostic in this
scenario:
```py
from typing import ClassVar

class C:
    x: ClassVar[int] = 1

c = C()
c.x = 3  # error: "Cannot assign to pure class variable `x` from an instance of type `C`"
```

## Test Plan

- New tests for the `typing.ClassVar` qualifier
- Fixed one TODO in `attributes.md`
This commit is contained in:
David Peter
2025-01-18 13:51:35 +01:00
committed by GitHub
parent 9730ff3a25
commit fb15da5694
5 changed files with 499 additions and 114 deletions

View File

@@ -1,6 +1,7 @@
use std::hash::Hash;
use std::iter;
use bitflags::bitflags;
use context::InferContext;
use diagnostic::{report_not_iterable, report_not_iterable_possibly_unbound};
use indexmap::IndexSet;
@@ -92,7 +93,7 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db>
// on inference from bindings.
let declarations = use_def.public_declarations(symbol);
let declared = declarations_ty(db, declarations);
let declared = declarations_ty(db, declarations).map(|SymbolAndQualifiers(ty, _)| ty);
match declared {
// Symbol is declared, trust the declared type
@@ -126,7 +127,7 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db>
Err((declared_ty, _)) => {
// Intentionally ignore conflicting declared types; that's not our problem,
// it's the problem of the module we are importing from.
declared_ty.into()
declared_ty.inner_ty().into()
}
}
@@ -246,7 +247,7 @@ pub(crate) fn binding_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> T
}
/// Infer the type of a declaration.
fn declaration_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
fn declaration_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> TypeAndQualifiers<'db> {
let inference = infer_definition_types(db, definition);
inference.declaration_ty(definition)
}
@@ -356,22 +357,50 @@ fn bindings_ty<'db>(
}
}
/// A type with declaredness information, and a set of type qualifiers.
///
/// This is used to represent the result of looking up the declared type. Consider this
/// example:
/// ```py
/// class C:
/// if flag:
/// variable: ClassVar[int]
/// ```
/// If we look up the declared type of `variable` in the scope of class `C`, we will get
/// the type `int`, a "declaredness" of [`Boundness::PossiblyUnbound`], and the information
/// that this comes with a [`TypeQualifiers::CLASS_VAR`] type qualifier.
pub(crate) struct SymbolAndQualifiers<'db>(Symbol<'db>, TypeQualifiers);
impl SymbolAndQualifiers<'_> {
fn is_class_var(&self) -> bool {
self.1.contains(TypeQualifiers::CLASS_VAR)
}
}
impl<'db> From<Symbol<'db>> for SymbolAndQualifiers<'db> {
fn from(symbol: Symbol<'db>) -> Self {
SymbolAndQualifiers(symbol, TypeQualifiers::empty())
}
}
impl<'db> From<Type<'db>> for SymbolAndQualifiers<'db> {
fn from(ty: Type<'db>) -> Self {
SymbolAndQualifiers(ty.into(), TypeQualifiers::empty())
}
}
/// The result of looking up a declared type from declarations; see [`declarations_ty`].
type DeclaredTypeResult<'db> = Result<Symbol<'db>, (Type<'db>, Box<[Type<'db>]>)>;
type DeclaredTypeResult<'db> =
Result<SymbolAndQualifiers<'db>, (TypeAndQualifiers<'db>, Box<[Type<'db>]>)>;
/// Build a declared type from a [`DeclarationsIterator`].
///
/// If there is only one declaration, or all declarations declare the same type, returns
/// `Ok(declared_type)`. If there are conflicting declarations, returns
/// `Err((union_of_declared_types, conflicting_declared_types))`.
/// `Ok(..)`. If there are conflicting declarations, returns an `Err(..)` variant with
/// a union of the declared types as well as a list of all conflicting types.
///
/// If undeclared is a possibility, `undeclared_ty` type will be part of the return type but it
/// will not be considered to be conflicting with any other types.
///
/// # Panics
/// Will panic if there are no declarations and no `undeclared_ty` is provided. This is a logic
/// error, as any symbol with zero live declarations clearly must be undeclared, and the caller
/// should provide an `undeclared_ty`.
/// This function also returns declaredness information (see [`Symbol`]) and a set of
/// [`TypeQualifiers`] that have been specified on the declaration(s).
fn declarations_ty<'db>(
db: &'db dyn Db,
declarations: DeclarationsIterator<'_, 'db>,
@@ -408,14 +437,19 @@ fn declarations_ty<'db>(
if let Some(first) = types.next() {
let mut conflicting: Vec<Type<'db>> = vec![];
let declared_ty = if let Some(second) = types.next() {
let mut builder = UnionBuilder::new(db).add(first);
let ty_first = first.inner_ty();
let mut qualifiers = first.qualifiers();
let mut builder = UnionBuilder::new(db).add(ty_first);
for other in std::iter::once(second).chain(types) {
if !first.is_equivalent_to(db, other) {
conflicting.push(other);
let other_ty = other.inner_ty();
if !ty_first.is_equivalent_to(db, other_ty) {
conflicting.push(other_ty);
}
builder = builder.add(other);
builder = builder.add(other_ty);
qualifiers = qualifiers.union(other.qualifiers());
}
builder.build()
TypeAndQualifiers::new(builder.build(), qualifiers)
} else {
first
};
@@ -428,15 +462,20 @@ fn declarations_ty<'db>(
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
};
Ok(Symbol::Type(declared_ty, boundness))
Ok(SymbolAndQualifiers(
Symbol::Type(declared_ty.inner_ty(), boundness),
declared_ty.qualifiers(),
))
} else {
Err((
declared_ty,
std::iter::once(first).chain(conflicting).collect(),
std::iter::once(first.inner_ty())
.chain(conflicting)
.collect(),
))
}
} else {
Ok(Symbol::Unbound)
Ok(Symbol::Unbound.into())
}
}
@@ -1661,7 +1700,10 @@ impl<'db> Type<'db> {
(Some(KnownClass::VersionInfo), "minor") => {
Type::IntLiteral(Program::get(db).python_version(db).minor.into()).into()
}
_ => class.instance_member(db, name),
_ => {
let SymbolAndQualifiers(symbol, _) = class.instance_member(db, name);
symbol
}
},
Type::Union(union) => {
@@ -2285,11 +2327,18 @@ impl<'db> Type<'db> {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::ClassVar) => {
// TODO: A bare `ClassVar` should rather be treated as if the symbol was not
// declared at all.
Ok(Type::unknown())
}
Type::KnownInstance(KnownInstanceType::ClassVar) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::ClassVarInTypeExpression
],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::Final) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::FinalInTypeExpression
],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::Literal) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareLiteral],
fallback_type: Type::unknown(),
@@ -2466,6 +2515,60 @@ impl std::fmt::Display for DynamicType {
}
}
bitflags! {
/// Type qualifiers that appear in an annotation expression.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct TypeQualifiers: u8 {
/// `typing.ClassVar`
const CLASS_VAR = 1 << 0;
/// `typing.Final`
const FINAL = 1 << 1;
}
}
/// When inferring the type of an annotation expression, we can also encounter type qualifiers
/// such as `ClassVar` or `Final`. These do not affect the inferred type itself, but rather
/// control how a particular symbol can be accessed or modified. This struct holds a type and
/// a set of type qualifiers.
///
/// Example: `Annotated[ClassVar[tuple[int]], "metadata"]` would have type `tuple[int]` and the
/// qualifier `ClassVar`.
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
pub(crate) struct TypeAndQualifiers<'db> {
inner: Type<'db>,
qualifiers: TypeQualifiers,
}
impl<'db> TypeAndQualifiers<'db> {
pub(crate) fn new(inner: Type<'db>, qualifiers: TypeQualifiers) -> Self {
Self { inner, qualifiers }
}
/// Forget about type qualifiers and only return the inner type.
pub(crate) fn inner_ty(&self) -> Type<'db> {
self.inner
}
/// Insert/add an additional type qualifier.
pub(crate) fn add_qualifier(&mut self, qualifier: TypeQualifiers) {
self.qualifiers |= qualifier;
}
/// Return the set of type qualifiers.
pub(crate) fn qualifiers(&self) -> TypeQualifiers {
self.qualifiers
}
}
impl<'db> From<Type<'db>> for TypeAndQualifiers<'db> {
fn from(inner: Type<'db>) -> Self {
Self {
inner,
qualifiers: TypeQualifiers::empty(),
}
}
}
/// Error struct providing information on type(s) that were deemed to be invalid
/// in a type expression context, and the type we should therefore fallback to
/// for the problematic type expression.
@@ -2499,6 +2602,10 @@ enum InvalidTypeExpression {
BareAnnotated,
/// `x: Literal` is invalid as an annotation
BareLiteral,
/// The `ClassVar` type qualifier was used in a type expression
ClassVarInTypeExpression,
/// The `Final` type qualifier was used in a type expression
FinalInTypeExpression,
}
impl InvalidTypeExpression {
@@ -2506,6 +2613,8 @@ impl InvalidTypeExpression {
match self {
Self::BareAnnotated => "`Annotated` requires at least two arguments when used in an annotation or type expression",
Self::BareLiteral => "`Literal` requires at least one argument when used in a type expression",
Self::ClassVarInTypeExpression => "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)",
Self::FinalInTypeExpression => "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)",
}
}
}
@@ -3980,15 +4089,16 @@ impl<'db> Class<'db> {
/// defined attribute that is only present in a method (typically `__init__`).
///
/// The attribute might also be defined in a superclass of this class.
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
for superclass in self.iter_mro(db) {
match superclass {
ClassBase::Dynamic(_) => {
return todo_type!("instance attribute on class with dynamic base").into();
}
ClassBase::Class(class) => {
let member = class.own_instance_member(db, name);
if !member.is_unbound() {
if let member @ SymbolAndQualifiers(Symbol::Type(_, _), _) =
class.own_instance_member(db, name)
{
return member;
}
}
@@ -4002,7 +4112,7 @@ impl<'db> Class<'db> {
/// A helper function for `instance_member` that looks up the `name` attribute only on
/// this class, not on its superclasses.
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
// TODO: There are many things that are not yet implemented here:
// - `typing.ClassVar` and `typing.Final`
// - Proper diagnostics
@@ -4018,7 +4128,7 @@ impl<'db> Class<'db> {
let declarations = use_def.public_declarations(symbol);
match declarations_ty(db, declarations) {
Ok(Symbol::Type(declared_ty, _)) => {
Ok(SymbolAndQualifiers(Symbol::Type(declared_ty, _), qualifiers)) => {
if let Some(function) = declared_ty.into_function_literal() {
// TODO: Eventually, we are going to process all decorators correctly. This is
// just a temporary heuristic to provide a broad categorization into properties
@@ -4029,28 +4139,31 @@ impl<'db> Class<'db> {
todo_type!("bound method").into()
}
} else {
Symbol::Type(declared_ty, Boundness::Bound)
SymbolAndQualifiers(Symbol::Type(declared_ty, Boundness::Bound), qualifiers)
}
}
Ok(Symbol::Unbound) => {
Ok(SymbolAndQualifiers(Symbol::Unbound, qualifiers)) => {
let bindings = use_def.public_bindings(symbol);
let inferred_ty = bindings_ty(db, bindings);
match inferred_ty {
Symbol::Type(ty, _) => Symbol::Type(
UnionType::from_elements(db, [Type::unknown(), ty]),
Boundness::Bound,
Symbol::Type(ty, _) => SymbolAndQualifiers(
Symbol::Type(
UnionType::from_elements(db, [Type::unknown(), ty]),
Boundness::Bound,
),
qualifiers,
),
Symbol::Unbound => Symbol::Unbound,
Symbol::Unbound => SymbolAndQualifiers(Symbol::Unbound, qualifiers),
}
}
Err((declared_ty, _)) => {
Err((declared_ty, _conflicting_declarations)) => {
// Ignore conflicting declarations
declared_ty.into()
SymbolAndQualifiers(declared_ty.inner_ty().into(), declared_ty.qualifiers())
}
}
} else {
Symbol::Unbound
Symbol::Unbound.into()
}
}

View File

@@ -1,5 +1,4 @@
use super::context::InferContext;
use crate::declare_lint;
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::suppression::FileSuppressionId;
use crate::types::string_annotation::{
@@ -7,7 +6,8 @@ use crate::types::string_annotation::{
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
RAW_STRING_TYPE_ANNOTATION,
};
use crate::types::{ClassLiteralType, Type};
use crate::types::{ClassLiteralType, KnownInstanceType, Type};
use crate::{declare_lint, Db};
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_python_ast::{self as ast, AnyNodeRef};
@@ -59,6 +59,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&UNSUPPORTED_OPERATOR);
registry.register_lint(&ZERO_STEPSIZE_IN_SLICE);
registry.register_lint(&STATIC_ASSERT_ERROR);
registry.register_lint(&INVALID_ATTRIBUTE_ACCESS);
// String annotations
registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION);
@@ -750,6 +751,25 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Makes sure that instance attribute accesses are valid.
///
/// ## Examples
/// ```python
/// class C:
/// var: ClassVar[int] = 1
///
/// C.var = 3 # okay
/// C().var = 3 # error: Cannot assign to class variable
/// ```
pub(crate) static INVALID_ATTRIBUTE_ACCESS = {
summary: "Invalid attribute access",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct TypeCheckDiagnostic {
pub(crate) id: DiagnosticId,
@@ -1060,3 +1080,18 @@ pub(crate) fn report_base_with_incompatible_slots(context: &InferContext, node:
format_args!("Class base has incompatible `__slots__`"),
);
}
pub(crate) fn report_invalid_arguments_to_annotated<'db>(
db: &'db dyn Db,
context: &InferContext<'db>,
subscript: &ast::ExprSubscript,
) {
context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Special form `{}` expected at least 2 arguments (one type and at least one metadata element)",
KnownInstanceType::Annotated.repr(db)
),
);
}

View File

@@ -51,9 +51,10 @@ use crate::semantic_index::SemanticIndex;
use crate::stdlib::builtins_module_scope;
use crate::types::call::{Argument, CallArguments};
use crate::types::diagnostic::{
report_invalid_assignment, report_unresolved_module, TypeCheckDiagnostics, CALL_NON_CALLABLE,
CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE, INCONSISTENT_MRO, INVALID_BASE,
report_invalid_arguments_to_annotated, report_invalid_assignment, report_unresolved_module,
TypeCheckDiagnostics, CALL_NON_CALLABLE, CALL_POSSIBLY_UNBOUND_METHOD,
CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO,
DUPLICATE_BASE, INCONSISTENT_MRO, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
INVALID_CONTEXT_MANAGER, INVALID_DECLARATION, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_ATTRIBUTE, POSSIBLY_UNBOUND_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
@@ -65,8 +66,9 @@ use crate::types::{
typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, DynamicType,
FunctionType, InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome,
KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind,
SliceLiteralType, SubclassOfType, Symbol, Truthiness, TupleType, Type, TypeAliasType,
TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
TypeVarInstance, UnionBuilder, UnionType,
};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
@@ -113,7 +115,7 @@ fn infer_definition_types_cycle_recovery<'db>(
let mut inference = TypeInference::empty(input.scope(db));
let category = input.category(db);
if category.is_declaration() {
inference.declarations.insert(input, Type::unknown());
inference.declarations.insert(input, Type::unknown().into());
}
if category.is_binding() {
inference.bindings.insert(input, Type::unknown());
@@ -241,8 +243,8 @@ pub(crate) struct TypeInference<'db> {
/// The types of every binding in this region.
bindings: FxHashMap<Definition<'db>, Type<'db>>,
/// The types of every declaration in this region.
declarations: FxHashMap<Definition<'db>, Type<'db>>,
/// The types and type qualifiers of every declaration in this region.
declarations: FxHashMap<Definition<'db>, TypeAndQualifiers<'db>>,
/// The definitions that are deferred.
deferred: FxHashSet<Definition<'db>>,
@@ -281,7 +283,7 @@ impl<'db> TypeInference<'db> {
}
#[track_caller]
pub(crate) fn declaration_ty(&self, definition: Definition<'db>) -> Type<'db> {
pub(crate) fn declaration_ty(&self, definition: Definition<'db>) -> TypeAndQualifiers<'db> {
self.declarations[&definition]
}
@@ -318,7 +320,7 @@ enum DeclaredAndInferredType<'db> {
AreTheSame(Type<'db>),
/// Declared and inferred types might be different, we need to check assignability.
MightBeDifferent {
declared_ty: Type<'db>,
declared_ty: TypeAndQualifiers<'db>,
inferred_ty: Type<'db>,
},
}
@@ -563,7 +565,9 @@ impl<'db> TypeInferenceBuilder<'db> {
.filter_map(|(definition, ty)| {
// Filter out class literals that result from imports
if let DefinitionKind::Class(class) = definition.kind(self.db()) {
ty.into_class_literal().map(|ty| (ty.class, class.node()))
ty.inner_ty()
.into_class_literal()
.map(|ty| (ty.class, class.node()))
} else {
None
}
@@ -855,7 +859,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let declarations = use_def.declarations_at_binding(binding);
let mut bound_ty = ty;
let declared_ty = declarations_ty(self.db(), declarations)
.map(|s| s.ignore_possibly_unbound().unwrap_or(Type::unknown()))
.map(|SymbolAndQualifiers(s, _)| s.ignore_possibly_unbound().unwrap_or(Type::unknown()))
.unwrap_or_else(|(ty, conflicting)| {
// TODO point out the conflicting declarations in the diagnostic?
let symbol_table = self.index.symbol_table(binding.file_scope(self.db()));
@@ -868,7 +872,7 @@ impl<'db> TypeInferenceBuilder<'db> {
conflicting.display(self.db())
),
);
ty
ty.inner_ty()
});
if !bound_ty.is_assignable_to(self.db(), declared_ty) {
report_invalid_assignment(&self.context, node, declared_ty, bound_ty);
@@ -879,7 +883,12 @@ impl<'db> TypeInferenceBuilder<'db> {
self.types.bindings.insert(binding, bound_ty);
}
fn add_declaration(&mut self, node: AnyNodeRef, declaration: Definition<'db>, ty: Type<'db>) {
fn add_declaration(
&mut self,
node: AnyNodeRef,
declaration: Definition<'db>,
ty: TypeAndQualifiers<'db>,
) {
debug_assert!(declaration.is_declaration(self.db()));
let use_def = self.index.use_def_map(declaration.file_scope(self.db()));
let prior_bindings = use_def.bindings_at_declaration(declaration);
@@ -887,7 +896,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let inferred_ty = bindings_ty(self.db(), prior_bindings)
.ignore_possibly_unbound()
.unwrap_or(Type::Never);
let ty = if inferred_ty.is_assignable_to(self.db(), ty) {
let ty = if inferred_ty.is_assignable_to(self.db(), ty.inner_ty()) {
ty
} else {
self.context.report_lint(
@@ -895,11 +904,11 @@ impl<'db> TypeInferenceBuilder<'db> {
node,
format_args!(
"Cannot declare type `{}` for inferred type `{}`",
ty.display(self.db()),
ty.inner_ty().display(self.db()),
inferred_ty.display(self.db())
),
);
Type::unknown()
Type::unknown().into()
};
self.types.declarations.insert(declaration, ty);
}
@@ -913,23 +922,28 @@ impl<'db> TypeInferenceBuilder<'db> {
debug_assert!(definition.is_binding(self.db()));
debug_assert!(definition.is_declaration(self.db()));
let (declared_ty, inferred_ty) = match declared_and_inferred_ty {
DeclaredAndInferredType::AreTheSame(ty) => (ty, ty),
let (declared_ty, inferred_ty) = match *declared_and_inferred_ty {
DeclaredAndInferredType::AreTheSame(ty) => (ty.into(), ty),
DeclaredAndInferredType::MightBeDifferent {
declared_ty,
inferred_ty,
} => {
if inferred_ty.is_assignable_to(self.db(), *declared_ty) {
if inferred_ty.is_assignable_to(self.db(), declared_ty.inner_ty()) {
(declared_ty, inferred_ty)
} else {
report_invalid_assignment(&self.context, node, *declared_ty, *inferred_ty);
report_invalid_assignment(
&self.context,
node,
declared_ty.inner_ty(),
inferred_ty,
);
// if the assignment is invalid, fall back to assuming the annotation is correct
(declared_ty, declared_ty)
(declared_ty, declared_ty.inner_ty())
}
}
};
self.types.declarations.insert(definition, *declared_ty);
self.types.bindings.insert(definition, *inferred_ty);
self.types.declarations.insert(definition, declared_ty);
self.types.bindings.insert(definition, inferred_ty);
}
fn add_unknown_declaration_with_binding(
@@ -1220,7 +1234,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let declared_and_inferred_ty = if let Some(default_ty) = default_ty {
if default_ty.is_assignable_to(self.db(), declared_ty) {
DeclaredAndInferredType::MightBeDifferent {
declared_ty,
declared_ty: declared_ty.into(),
inferred_ty: UnionType::from_elements(self.db(), [declared_ty, default_ty]),
}
} else if self.in_stub()
@@ -2066,7 +2080,7 @@ impl<'db> TypeInferenceBuilder<'db> {
);
// Handle various singletons.
if let Type::Instance(InstanceType { class }) = declared_ty {
if let Type::Instance(InstanceType { class }) = declared_ty.inner_ty() {
if class.is_known(self.db(), KnownClass::SpecialForm) {
if let Some(name_expr) = target.as_name_expr() {
if let Some(known_instance) = KnownInstanceType::try_from_file_and_name(
@@ -2074,7 +2088,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.file(),
&name_expr.id,
) {
declared_ty = Type::KnownInstance(known_instance);
declared_ty.inner = Type::KnownInstance(known_instance);
}
}
}
@@ -2083,7 +2097,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Some(value) = value.as_deref() {
let inferred_ty = self.infer_expression(value);
let inferred_ty = if self.in_stub() && value.is_ellipsis_literal_expr() {
declared_ty
declared_ty.inner_ty()
} else {
inferred_ty
};
@@ -3391,14 +3405,33 @@ impl<'db> TypeInferenceBuilder<'db> {
fn infer_attribute_expression(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
let ast::ExprAttribute {
value,
attr: _,
attr,
range: _,
ctx,
} = attribute;
match ctx {
ExprContext::Load => self.infer_attribute_load(attribute),
ExprContext::Store | ExprContext::Del => {
ExprContext::Store => {
let value_ty = self.infer_expression(value);
if let Type::Instance(instance) = value_ty {
let instance_member = instance.class.instance_member(self.db(), attr);
if instance_member.is_class_var() {
self.context.report_lint(
&INVALID_ATTRIBUTE_ACCESS,
attribute.into(),
format_args!(
"Cannot assign to ClassVar `{attr}` from an instance of type `{ty}`",
ty = value_ty.display(self.db()),
),
);
}
}
Type::Never
}
ExprContext::Del => {
self.infer_expression(value);
Type::Never
}
@@ -4698,7 +4731,7 @@ impl<'db> TypeInferenceBuilder<'db> {
&mut self,
annotation: &ast::Expr,
deferred_state: DeferredExpressionState,
) -> Type<'db> {
) -> TypeAndQualifiers<'db> {
let previous_deferred_state = std::mem::replace(&mut self.deferred_state, deferred_state);
let annotation_ty = self.infer_annotation_expression_impl(annotation);
self.deferred_state = previous_deferred_state;
@@ -4713,21 +4746,24 @@ impl<'db> TypeInferenceBuilder<'db> {
&mut self,
annotation: Option<&ast::Expr>,
deferred_state: DeferredExpressionState,
) -> Option<Type<'db>> {
) -> Option<TypeAndQualifiers<'db>> {
annotation.map(|expr| self.infer_annotation_expression(expr, deferred_state))
}
/// Implementation of [`infer_annotation_expression`].
///
/// [`infer_annotation_expression`]: TypeInferenceBuilder::infer_annotation_expression
fn infer_annotation_expression_impl(&mut self, annotation: &ast::Expr) -> Type<'db> {
fn infer_annotation_expression_impl(
&mut self,
annotation: &ast::Expr,
) -> TypeAndQualifiers<'db> {
// https://typing.readthedocs.io/en/latest/spec/annotations.html#grammar-token-expression-grammar-annotation_expression
let annotation_ty = match annotation {
// String annotations: https://typing.readthedocs.io/en/latest/spec/annotations.html#string-annotations
ast::Expr::StringLiteral(string) => self.infer_string_annotation_expression(string),
// Annotation expressions also get special handling for `*args` and `**kwargs`.
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
ast::Expr::Starred(starred) => self.infer_starred_expression(starred).into(),
ast::Expr::BytesLiteral(bytes) => {
self.context.report_lint(
@@ -4735,7 +4771,7 @@ impl<'db> TypeInferenceBuilder<'db> {
bytes.into(),
format_args!("Type expressions cannot use bytes literal"),
);
Type::unknown()
Type::unknown().into()
}
ast::Expr::FString(fstring) => {
@@ -4745,21 +4781,126 @@ impl<'db> TypeInferenceBuilder<'db> {
format_args!("Type expressions cannot use f-strings"),
);
self.infer_fstring_expression(fstring);
Type::unknown()
Type::unknown().into()
}
ast::Expr::Name(name) => match name.ctx {
ast::ExprContext::Load => {
let name_expr_ty = self.infer_name_expression(name);
match name_expr_ty {
Type::KnownInstance(KnownInstanceType::ClassVar) => {
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::CLASS_VAR)
}
Type::KnownInstance(KnownInstanceType::Final) => {
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::FINAL)
}
_ => name_expr_ty
.in_type_expression(self.db())
.unwrap_or_else(|error| {
error.into_fallback_type(&self.context, annotation)
})
.into(),
}
}
ast::ExprContext::Invalid => Type::unknown().into(),
ast::ExprContext::Store | ast::ExprContext::Del => todo_type!().into(),
},
ast::Expr::Subscript(subscript @ ast::ExprSubscript { value, slice, .. }) => {
let value_ty = self.infer_expression(value);
let slice = &**slice;
match value_ty {
Type::KnownInstance(KnownInstanceType::Annotated) => {
// This branch is similar to the corresponding branch in `infer_parameterized_known_instance_type_expression`, but
// `Annotated[…]` can appear both in annotation expressions and in type expressions, and needs to be handled slightly
// differently in each case (calling either `infer_type_expression_*` or `infer_annotation_expression_*`).
if let ast::Expr::Tuple(ast::ExprTuple {
elts: arguments, ..
}) = slice
{
if arguments.len() < 2 {
report_invalid_arguments_to_annotated(
self.db(),
&self.context,
subscript,
);
}
if let [inner_annotation, metadata @ ..] = &arguments[..] {
for element in metadata {
self.infer_expression(element);
}
let inner_annotation_ty =
self.infer_annotation_expression_impl(inner_annotation);
self.store_expression_type(slice, inner_annotation_ty.inner_ty());
inner_annotation_ty
} else {
self.infer_type_expression(slice);
Type::unknown().into()
}
} else {
report_invalid_arguments_to_annotated(
self.db(),
&self.context,
subscript,
);
self.infer_annotation_expression_impl(slice)
}
}
Type::KnownInstance(
known_instance @ (KnownInstanceType::ClassVar | KnownInstanceType::Final),
) => match slice {
ast::Expr::Tuple(..) => {
self.context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Type qualifier `{type_qualifier}` expects exactly one type parameter",
type_qualifier = known_instance.repr(self.db()),
),
);
Type::unknown().into()
}
_ => {
let mut type_and_qualifiers =
self.infer_annotation_expression_impl(slice);
match known_instance {
KnownInstanceType::ClassVar => {
type_and_qualifiers.add_qualifier(TypeQualifiers::CLASS_VAR);
}
KnownInstanceType::Final => {
type_and_qualifiers.add_qualifier(TypeQualifiers::FINAL);
}
_ => unreachable!(),
}
type_and_qualifiers
}
},
_ => self
.infer_subscript_type_expression_no_store(subscript, slice, value_ty)
.into(),
}
}
// All other annotation expressions are (possibly) valid type expressions, so handle
// them there instead.
type_expr => self.infer_type_expression_no_store(type_expr),
type_expr => self.infer_type_expression_no_store(type_expr).into(),
};
self.store_expression_type(annotation, annotation_ty);
self.store_expression_type(annotation, annotation_ty.inner_ty());
annotation_ty
}
/// Infer the type of a string annotation expression.
fn infer_string_annotation_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> {
fn infer_string_annotation_expression(
&mut self,
string: &ast::ExprStringLiteral,
) -> TypeAndQualifiers<'db> {
match parse_string_annotation(&self.context, string) {
Some(parsed) => {
// String annotations are always evaluated in the deferred context.
@@ -4768,7 +4909,7 @@ impl<'db> TypeInferenceBuilder<'db> {
DeferredExpressionState::InStringAnnotation,
)
}
None => Type::unknown(),
None => Type::unknown().into(),
}
}
}
@@ -4855,16 +4996,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let value_ty = self.infer_expression(value);
match value_ty {
Type::ClassLiteral(class_literal_ty) => {
match class_literal_ty.class.known(self.db()) {
Some(KnownClass::Tuple) => self.infer_tuple_type_expression(slice),
Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice),
_ => self.infer_subscript_type_expression(subscript, value_ty),
}
}
_ => self.infer_subscript_type_expression(subscript, value_ty),
}
self.infer_subscript_type_expression_no_store(subscript, slice, value_ty)
}
ast::Expr::BinOp(binary) => {
@@ -4980,6 +5112,22 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
fn infer_subscript_type_expression_no_store(
&mut self,
subscript: &ast::ExprSubscript,
slice: &ast::Expr,
value_ty: Type<'db>,
) -> Type<'db> {
match value_ty {
Type::ClassLiteral(class_literal_ty) => match class_literal_ty.class.known(self.db()) {
Some(KnownClass::Tuple) => self.infer_tuple_type_expression(slice),
Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice),
_ => self.infer_subscript_type_expression(subscript, value_ty),
},
_ => self.infer_subscript_type_expression(subscript, value_ty),
}
}
/// Infer the type of a string type expression.
fn infer_string_type_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> {
match parse_string_annotation(&self.context, string) {
@@ -5163,22 +5311,11 @@ impl<'db> TypeInferenceBuilder<'db> {
let arguments_slice = &*subscript.slice;
match known_instance {
KnownInstanceType::Annotated => {
let report_invalid_arguments = || {
self.context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Special form `{}` expected at least 2 arguments (one type and at least one metadata element)",
known_instance.repr(self.db())
),
);
};
let ast::Expr::Tuple(ast::ExprTuple {
elts: arguments, ..
}) = arguments_slice
else {
report_invalid_arguments();
report_invalid_arguments_to_annotated(self.db(), &self.context, subscript);
// `Annotated[]` with less than two arguments is an error at runtime.
// However, we still treat `Annotated[T]` as `T` here for the purpose of
@@ -5188,7 +5325,7 @@ impl<'db> TypeInferenceBuilder<'db> {
};
if arguments.len() < 2 {
report_invalid_arguments();
report_invalid_arguments_to_annotated(self.db(), &self.context, subscript);
}
let [type_expr, metadata @ ..] = &arguments[..] else {
@@ -5345,13 +5482,16 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_type_expression(arguments_slice);
todo_type!("`NotRequired[]` type qualifier")
}
KnownInstanceType::ClassVar => {
let ty = self.infer_type_expression(arguments_slice);
ty
}
KnownInstanceType::Final => {
self.infer_type_expression(arguments_slice);
todo_type!("`Final[]` type qualifier")
KnownInstanceType::ClassVar | KnownInstanceType::Final => {
self.context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Type qualifier `{}` is not allowed in type expressions (only in annotation expressions)",
known_instance.repr(self.db())
),
);
self.infer_type_expression(arguments_slice)
}
KnownInstanceType::Required => {
self.infer_type_expression(arguments_slice);