Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -1443,7 +1443,7 @@ from typing import List, Dict
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
InvalidList = List[1]

# error: [invalid-type-form] "`typing.List` requires exactly one argument"
# error: [invalid-type-form] "`typing.List` requires exactly 1 argument, got 2"
ListTooManyArgs = List[int, str]

# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
Expand All @@ -1452,10 +1452,10 @@ InvalidDict1 = Dict[1, str]
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
InvalidDict2 = Dict[str, 2]

# error: [invalid-type-form] "`typing.Dict` requires exactly two arguments, got 1"
# error: [invalid-type-form] "`typing.Dict` requires exactly 2 arguments, got 1"
DictTooFewArgs = Dict[str]

# error: [invalid-type-form] "`typing.Dict` requires exactly two arguments, got 3"
# error: [invalid-type-form] "`typing.Dict` requires exactly 2 arguments, got 3"
DictTooManyArgs = Dict[str, int, float]

def _(
Expand All @@ -1470,7 +1470,7 @@ def _(
reveal_type(list_too_many_args) # revealed: list[Unknown]
reveal_type(invalid_dict1) # revealed: dict[Unknown, str]
reveal_type(invalid_dict2) # revealed: dict[str, Unknown]
reveal_type(dict_too_few_args) # revealed: dict[str, Unknown]
reveal_type(dict_too_few_args) # revealed: dict[Unknown, Unknown]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the only change in behaviour from this PR, which comes as a result of unifying the parsing of stdlib aliases between infer/builder.rs and infer/builder/type_expression.rs so that they now share a common implementation. Either the answer we give on main or the new answer seem fine to me, since this is an invalid type expression, but I can look into it if anybody strongly prefers the answer we have on main

reveal_type(dict_too_many_args) # revealed: dict[Unknown, Unknown]
```

Expand Down
156 changes: 28 additions & 128 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use compact_str::ToCompactString;
use infer::nearest_enclosing_class;
use itertools::{Either, Itertools};
use ruff_diagnostics::{Edit, Fix};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -63,8 +62,7 @@ use crate::types::function::{
FunctionType, KnownFunction,
};
use crate::types::generics::{
ApplySpecialization, InferableTypeVars, Specialization, bind_typevar, typing_self,
walk_generic_context,
ApplySpecialization, InferableTypeVars, Specialization, bind_typevar, walk_generic_context,
};
pub(crate) use crate::types::generics::{GenericContext, SpecializationBuilder};
use crate::types::mro::{Mro, MroIterator, StaticMroError};
Expand All @@ -75,6 +73,7 @@ pub(crate) use crate::types::narrow::{
use crate::types::newtype::NewType;
pub(crate) use crate::types::signatures::{Parameter, Parameters};
use crate::types::signatures::{ParameterForm, walk_signature};
use crate::types::special_form::{SpecialFormCategory, TypeQualifier};
use crate::types::tuple::{Tuple, TupleSpec, TupleSpecBuilder};
use crate::types::typed_dict::TypedDictField;
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
Expand Down Expand Up @@ -5991,133 +5990,33 @@ impl<'db> Type<'db> {
KnownInstanceType::LiteralStringAlias(ty) => Ok(ty.inner(db)),
},

Type::SpecialForm(special_form) => match special_form {
SpecialFormType::Never | SpecialFormType::NoReturn => Ok(Type::Never),
SpecialFormType::LiteralString => Ok(Type::literal_string()),
SpecialFormType::Any => Ok(Type::any()),
SpecialFormType::Unknown => Ok(Type::unknown()),
SpecialFormType::AlwaysTruthy => Ok(Type::AlwaysTruthy),
SpecialFormType::AlwaysFalsy => Ok(Type::AlwaysFalsy),

Type::SpecialForm(special_form) => match special_form.kind() {
// We treat `typing.Type` exactly the same as `builtins.type`:
SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)),
SpecialFormType::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())),

// Legacy `typing` aliases
SpecialFormType::List => Ok(KnownClass::List.to_instance(db)),
SpecialFormType::Dict => Ok(KnownClass::Dict.to_instance(db)),
SpecialFormType::Set => Ok(KnownClass::Set.to_instance(db)),
SpecialFormType::FrozenSet => Ok(KnownClass::FrozenSet.to_instance(db)),
SpecialFormType::ChainMap => Ok(KnownClass::ChainMap.to_instance(db)),
SpecialFormType::Counter => Ok(KnownClass::Counter.to_instance(db)),
SpecialFormType::DefaultDict => Ok(KnownClass::DefaultDict.to_instance(db)),
SpecialFormType::Deque => Ok(KnownClass::Deque.to_instance(db)),
SpecialFormType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)),

// TODO: Use an opt-in rule for a bare `Callable`
SpecialFormType::Callable => Ok(Type::Callable(CallableType::unknown(db))),

// Special case: `NamedTuple` in a type expression is understood to describe the type
// `tuple[object, ...] & <a protocol that any `NamedTuple` class would satisfy>`.
// This isn't very principled (since at runtime, `NamedTuple` is just a function),
// but it appears to be what users often expect, and it improves compatibility with
// other type checkers such as mypy.
// See conversation in https://github.com/astral-sh/ruff/pull/19915.
SpecialFormType::NamedTuple => Ok(IntersectionType::from_elements(
db,
[
Type::homogeneous_tuple(db, Type::object()),
KnownClass::NamedTupleLike.to_instance(db),
],
)),
SpecialFormType::TypingSelf => {
let index = semantic_index(db, scope_id.file(db));
let Some(class) = nearest_enclosing_class(db, index, scope_id) else {
return Err(InvalidTypeExpressionError {
fallback_type: Type::unknown(),
invalid_expressions: smallvec_inline![
InvalidTypeExpression::InvalidType(*self, scope_id)
],
});
SpecialFormCategory::Type => Ok(KnownClass::Type.to_instance(db)),
SpecialFormCategory::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())),
SpecialFormCategory::Callable => Ok(Type::Callable(CallableType::unknown(db))),
SpecialFormCategory::LegacyStdlibAlias(alias) => {
Ok(alias.aliased_class().to_instance(db))
}
SpecialFormCategory::Other(form) => {
form.in_type_expression(db, scope_id, typevar_binding_context)
}
SpecialFormCategory::TypeQualifier(qualifier) => {
let err = match qualifier {
TypeQualifier::Final | TypeQualifier::ClassVar => {
InvalidTypeExpression::TypeQualifier(qualifier)
}
TypeQualifier::ReadOnly
| TypeQualifier::NotRequired
| TypeQualifier::Required => {
InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier)
}
};

Ok(
typing_self(db, scope_id, typevar_binding_context, class.into())
.map(Type::TypeVar)
.unwrap_or(*self),
)
}
// We ensure that `typing.TypeAlias` used in the expected position (annotating an
// annotated assignment statement) doesn't reach here. Using it in any other type
// expression is an error.
SpecialFormType::TypeAlias => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![InvalidTypeExpression::TypeAlias],
fallback_type: Type::unknown(),
}),
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![InvalidTypeExpression::TypedDict],
fallback_type: Type::unknown(),
}),

SpecialFormType::Literal
| SpecialFormType::Union
| SpecialFormType::Intersection => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![
InvalidTypeExpression::RequiresArguments(*special_form)
],
fallback_type: Type::unknown(),
}),

SpecialFormType::Protocol => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![InvalidTypeExpression::Protocol],
fallback_type: Type::unknown(),
}),
SpecialFormType::Generic => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![InvalidTypeExpression::Generic],
fallback_type: Type::unknown(),
}),

SpecialFormType::Optional
| SpecialFormType::Not
| SpecialFormType::Top
| SpecialFormType::Bottom
| SpecialFormType::TypeOf
| SpecialFormType::TypeIs
| SpecialFormType::TypeGuard
| SpecialFormType::Unpack
| SpecialFormType::CallableTypeOf => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![
InvalidTypeExpression::RequiresOneArgument(*special_form)
],
fallback_type: Type::unknown(),
}),

SpecialFormType::Annotated | SpecialFormType::Concatenate => {
Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![
InvalidTypeExpression::RequiresTwoArguments(*special_form)
],
fallback_type: Type::unknown(),
})
}

SpecialFormType::ClassVar | SpecialFormType::Final => {
Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![
InvalidTypeExpression::TypeQualifier(*special_form)
],
invalid_expressions: smallvec::smallvec_inline![err],
fallback_type: Type::unknown(),
})
}

SpecialFormType::ReadOnly
| SpecialFormType::NotRequired
| SpecialFormType::Required => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec_inline![
InvalidTypeExpression::TypeQualifierRequiresOneArgument(*special_form)
],
fallback_type: Type::unknown(),
}),
},

Type::Union(union) => {
Expand Down Expand Up @@ -7956,9 +7855,10 @@ impl<'db> TypeAndQualifiers<'db> {
self.origin
}

/// Insert/add an additional type qualifier.
pub(crate) fn add_qualifier(&mut self, qualifier: TypeQualifiers) {
/// Return `self` with an additional qualifier added to the set of qualifiers.
pub(crate) fn with_qualifier(mut self, qualifier: TypeQualifiers) -> Self {
self.qualifiers |= qualifier;
self
}

/// Return the set of type qualifiers.
Expand Down Expand Up @@ -8043,10 +7943,10 @@ enum InvalidTypeExpression<'db> {
TypeAlias,
/// Type qualifiers are always invalid in *type expressions*,
/// but these ones are okay with 0 arguments in *annotation expressions*
TypeQualifier(SpecialFormType),
TypeQualifier(TypeQualifier),
/// Type qualifiers that are invalid in type expressions,
/// and which would require exactly one argument even if they appeared in an annotation expression
TypeQualifierRequiresOneArgument(SpecialFormType),
TypeQualifierRequiresOneArgument(TypeQualifier),
/// Some types are always invalid in type expressions
InvalidType(Type<'db>, ScopeId<'db>),
}
Expand Down
Loading