mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[ty] Support typing.TypeAliasType
(#18156)
## Summary Support direct uses of `typing.TypeAliasType`, as in: ```py from typing import TypeAliasType IntOrStr = TypeAliasType("IntOrStr", int | str) def f(x: IntOrStr) -> None: reveal_type(x) # revealed: int | str ``` closes https://github.com/astral-sh/ty/issues/392 ## Ecosystem The new false positive here: ```diff + error[invalid-type-form] altair/utils/core.py:49:53: The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...` ``` comes from the fact that we infer the second argument as a type expression now. We silence false positives for PEP695 `ParamSpec`s, but not for `P = ParamSpec("P")` inside `Callable[P, ...]`. ## Test Plan New Markdown tests
This commit is contained in:
parent
220137ca7b
commit
4c889d5251
8 changed files with 347 additions and 74 deletions
|
@ -87,3 +87,54 @@ type TypeAliasType2 = TypeOf[Alias2]
|
|||
static_assert(not is_equivalent_to(TypeAliasType1, TypeAliasType2))
|
||||
static_assert(is_disjoint_from(TypeAliasType1, TypeAliasType2))
|
||||
```
|
||||
|
||||
## Direct use of `TypeAliasType`
|
||||
|
||||
`TypeAliasType` can also be used directly. This is useful for versions of Python prior to 3.12.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
### Basic example
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAliasType, Union
|
||||
|
||||
IntOrStr = TypeAliasType("IntOrStr", Union[int, str])
|
||||
|
||||
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
|
||||
|
||||
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
|
||||
|
||||
def f(x: IntOrStr) -> None:
|
||||
reveal_type(x) # revealed: int | str
|
||||
```
|
||||
|
||||
### Generic example
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAliasType, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
|
||||
|
||||
def f(x: IntAnd[str]) -> None:
|
||||
reveal_type(x) # revealed: @Todo(Generic PEP-695 type alias)
|
||||
```
|
||||
|
||||
### Error cases
|
||||
|
||||
#### Name is not a string literal
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAliasType
|
||||
|
||||
def get_name() -> str:
|
||||
return "IntOrStr"
|
||||
|
||||
# error: [invalid-type-alias-type] "The name of a `typing.TypeAlias` must be a string literal"
|
||||
IntOrStr = TypeAliasType(get_name(), int | str)
|
||||
```
|
||||
|
|
|
@ -4012,6 +4012,45 @@ impl<'db> Type<'db> {
|
|||
Signatures::single(signature)
|
||||
}
|
||||
|
||||
Some(KnownClass::TypeAliasType) => {
|
||||
// ```py
|
||||
// def __new__(
|
||||
// cls,
|
||||
// name: str,
|
||||
// value: Any,
|
||||
// *,
|
||||
// type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = ()
|
||||
// ) -> Self: ...
|
||||
// ```
|
||||
let signature = CallableSignature::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_or_keyword(Name::new_static("name"))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
Parameter::positional_or_keyword(Name::new_static("value"))
|
||||
.with_annotated_type(Type::any())
|
||||
.type_form(),
|
||||
Parameter::keyword_only(Name::new_static("type_params"))
|
||||
.with_annotated_type(KnownClass::Tuple.to_specialized_instance(
|
||||
db,
|
||||
[UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
KnownClass::TypeVar.to_instance(db),
|
||||
KnownClass::ParamSpec.to_instance(db),
|
||||
KnownClass::TypeVarTuple.to_instance(db),
|
||||
],
|
||||
)],
|
||||
))
|
||||
.with_default_type(TupleType::empty(db)),
|
||||
]),
|
||||
None,
|
||||
),
|
||||
);
|
||||
Signatures::single(signature)
|
||||
}
|
||||
|
||||
Some(KnownClass::Property) => {
|
||||
let getter_signature = Signature::new(
|
||||
Parameters::new([
|
||||
|
@ -5318,7 +5357,7 @@ impl<'db> Type<'db> {
|
|||
Some(TypeDefinition::TypeVar(var.definition(db)))
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(type_alias) => {
|
||||
Some(TypeDefinition::TypeAlias(type_alias.definition(db)))
|
||||
type_alias.definition(db).map(TypeDefinition::TypeAlias)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
@ -7467,7 +7506,7 @@ impl<'db> ModuleLiteralType<'db> {
|
|||
}
|
||||
|
||||
#[salsa::interned(debug)]
|
||||
pub struct TypeAliasType<'db> {
|
||||
pub struct PEP695TypeAliasType<'db> {
|
||||
#[returns(ref)]
|
||||
pub name: ast::name::Name,
|
||||
|
||||
|
@ -7475,7 +7514,7 @@ pub struct TypeAliasType<'db> {
|
|||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> TypeAliasType<'db> {
|
||||
impl<'db> PEP695TypeAliasType<'db> {
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
let scope = self.rhs_scope(db);
|
||||
let type_alias_stmt_node = scope.node(db).expect_type_alias();
|
||||
|
@ -7492,6 +7531,43 @@ impl<'db> TypeAliasType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
#[salsa::interned(debug)]
|
||||
pub struct BareTypeAliasType<'db> {
|
||||
#[returns(ref)]
|
||||
pub name: ast::name::Name,
|
||||
pub definition: Option<Definition<'db>>,
|
||||
pub value: Type<'db>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, salsa::Update)]
|
||||
pub enum TypeAliasType<'db> {
|
||||
PEP695(PEP695TypeAliasType<'db>),
|
||||
Bare(BareTypeAliasType<'db>),
|
||||
}
|
||||
|
||||
impl<'db> TypeAliasType<'db> {
|
||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'db str {
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => type_alias.name(db),
|
||||
TypeAliasType::Bare(type_alias) => type_alias.name(db).as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => Some(type_alias.definition(db)),
|
||||
TypeAliasType::Bare(type_alias) => type_alias.definition(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => type_alias.value_type(db),
|
||||
TypeAliasType::Bare(type_alias) => type_alias.value(db),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
|
||||
pub(super) struct MetaclassCandidate<'db> {
|
||||
|
@ -8004,6 +8080,10 @@ impl<'db> TupleType<'db> {
|
|||
.to_specialized_instance(db, [UnionType::from_elements(db, self.elements(db))])
|
||||
}
|
||||
|
||||
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
|
||||
Type::Tuple(TupleType::new(db, Box::<[Type<'db>]>::from([])))
|
||||
}
|
||||
|
||||
pub(crate) fn from_elements<T: Into<Type<'db>>>(
|
||||
db: &'db dyn Db,
|
||||
types: impl IntoIterator<Item = T>,
|
||||
|
|
|
@ -41,6 +41,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
|||
registry.register_lint(&INVALID_EXCEPTION_CAUGHT);
|
||||
registry.register_lint(&INVALID_GENERIC_CLASS);
|
||||
registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE);
|
||||
registry.register_lint(&INVALID_TYPE_ALIAS_TYPE);
|
||||
registry.register_lint(&INVALID_METACLASS);
|
||||
registry.register_lint(&INVALID_OVERLOAD);
|
||||
registry.register_lint(&INVALID_PARAMETER_DEFAULT);
|
||||
|
@ -591,6 +592,27 @@ declare_lint! {
|
|||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for the creation of invalid `TypeAliasType`s
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// There are several requirements that you must follow when creating a `TypeAliasType`.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// from typing import TypeAliasType
|
||||
///
|
||||
/// IntOrStr = TypeAliasType("IntOrStr", int | str) # okay
|
||||
/// NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name must be a string literal
|
||||
/// ```
|
||||
pub(crate) static INVALID_TYPE_ALIAS_TYPE = {
|
||||
summary: "detects invalid TypeAliasType definitions",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for arguments to `metaclass=` that are invalid.
|
||||
|
|
|
@ -71,26 +71,26 @@ use crate::types::diagnostic::{
|
|||
CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, INCONSISTENT_MRO,
|
||||
INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
|
||||
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE,
|
||||
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_VARIABLE_CONSTRAINTS,
|
||||
POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE,
|
||||
UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR, report_implicit_return_type,
|
||||
report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable,
|
||||
report_invalid_assignment, report_invalid_attribute_assignment,
|
||||
report_invalid_generator_function_return_type, report_invalid_return_type,
|
||||
report_possibly_unbound_attribute,
|
||||
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM,
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics,
|
||||
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
|
||||
report_implicit_return_type, report_invalid_arguments_to_annotated,
|
||||
report_invalid_arguments_to_callable, report_invalid_assignment,
|
||||
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
||||
report_invalid_return_type, report_possibly_unbound_attribute,
|
||||
};
|
||||
use crate::types::generics::GenericContext;
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
CallDunderError, CallableSignature, CallableType, ClassLiteral, ClassType, DataclassParams,
|
||||
DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder,
|
||||
IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy,
|
||||
MetaclassCandidate, Parameter, ParameterForm, Parameters, Signature, Signatures,
|
||||
StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
|
||||
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
|
||||
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type,
|
||||
todo_type,
|
||||
BareTypeAliasType, CallDunderError, CallableSignature, CallableType, ClassLiteral, ClassType,
|
||||
DataclassParams, DynamicType, FunctionDecorators, FunctionType, GenericAlias,
|
||||
IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType,
|
||||
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
|
||||
Parameters, Signature, Signatures, StringLiteralType, SubclassOfType, Symbol,
|
||||
SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
|
||||
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
|
||||
TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
|
||||
};
|
||||
use crate::unpack::{Unpack, UnpackPosition};
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
|
@ -2374,12 +2374,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.node_scope(NodeWithScopeRef::TypeAlias(type_alias))
|
||||
.to_scope_id(self.db(), self.file());
|
||||
|
||||
let type_alias_ty =
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::new(
|
||||
let type_alias_ty = Type::KnownInstance(KnownInstanceType::TypeAliasType(
|
||||
TypeAliasType::PEP695(PEP695TypeAliasType::new(
|
||||
self.db(),
|
||||
&type_alias.name.as_name_expr().unwrap().id,
|
||||
rhs_scope,
|
||||
)));
|
||||
)),
|
||||
));
|
||||
|
||||
self.add_declaration_with_binding(
|
||||
type_alias.into(),
|
||||
|
@ -4860,6 +4861,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
| KnownClass::Super
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::NamedTuple
|
||||
| KnownClass::TypeAliasType
|
||||
)
|
||||
)
|
||||
// temporary special-casing for all subclasses of `enum.Enum`
|
||||
|
@ -5363,6 +5365,50 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
));
|
||||
}
|
||||
|
||||
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().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,
|
||||
) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The name of a `typing.TypeAlias` must be a string literal",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue