mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:05:08 +00:00
[ty] Induct into instances and subclasses when finding and applying generics (#18052)
We were not inducting into instance types and subclass-of types when looking for legacy typevars, nor when apply specializations. This addresses https://github.com/astral-sh/ruff/pull/17832#discussion_r2081502056 ```py from __future__ import annotations from typing import TypeVar, Any, reveal_type S = TypeVar("S") class Foo[T]: def method(self, other: Foo[S]) -> Foo[T | S]: ... # type: ignore[invalid-return-type] def f(x: Foo[Any], y: Foo[Any]): reveal_type(x.method(y)) # revealed: `Foo[Any | S]`, but should be `Foo[Any]` ``` We were not detecting that `S` made `method` generic, since we were not finding it when searching the function signature for legacy typevars.
This commit is contained in:
parent
7e9b0df18a
commit
f301931159
9 changed files with 269 additions and 68 deletions
|
@ -5048,7 +5048,7 @@ impl<'db> Type<'db> {
|
|||
),
|
||||
|
||||
Type::ProtocolInstance(instance) => {
|
||||
Type::ProtocolInstance(instance.apply_specialization(db, type_mapping))
|
||||
Type::ProtocolInstance(instance.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
|
||||
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
|
||||
|
@ -5080,12 +5080,13 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Type::GenericAlias(generic) => {
|
||||
let specialization = generic
|
||||
.specialization(db)
|
||||
.apply_type_mapping(db, type_mapping);
|
||||
Type::GenericAlias(GenericAlias::new(db, generic.origin(db), specialization))
|
||||
Type::GenericAlias(generic.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
|
||||
Type::SubclassOf(subclass_of) => Type::SubclassOf(
|
||||
subclass_of.apply_type_mapping(db, type_mapping),
|
||||
),
|
||||
|
||||
Type::PropertyInstance(property) => {
|
||||
Type::PropertyInstance(property.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
|
@ -5125,9 +5126,6 @@ impl<'db> Type<'db> {
|
|||
// explicitly (via a subscript expression) or implicitly (via a call), and not because
|
||||
// some other generic context's specialization is applied to it.
|
||||
| Type::ClassLiteral(_)
|
||||
// SubclassOf contains a ClassType, which has already been specialized if needed, like
|
||||
// above with BoundMethod's self_instance.
|
||||
| Type::SubclassOf(_)
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::LiteralString
|
||||
|
@ -5202,7 +5200,19 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Type::GenericAlias(alias) => {
|
||||
alias.specialization(db).find_legacy_typevars(db, typevars);
|
||||
alias.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
|
||||
Type::NominalInstance(instance) => {
|
||||
instance.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
|
||||
Type::ProtocolInstance(instance) => {
|
||||
instance.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
|
||||
Type::SubclassOf(subclass_of) => {
|
||||
subclass_of.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
|
||||
Type::Dynamic(_)
|
||||
|
@ -5215,15 +5225,12 @@ impl<'db> Type<'db> {
|
|||
| Type::DataclassTransformer(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::SubclassOf(_)
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::KnownInstance(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::types::generics::{GenericContext, Specialization, TypeMapping};
|
|||
use crate::types::signatures::{Parameter, Parameters};
|
||||
use crate::types::{
|
||||
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature,
|
||||
TypeVarInstance,
|
||||
};
|
||||
use crate::{
|
||||
module_resolver::file_to_module,
|
||||
|
@ -31,7 +32,7 @@ use crate::{
|
|||
definition_expression_type, CallArgumentTypes, CallError, CallErrorKind, DynamicType,
|
||||
MetaclassCandidate, TupleType, UnionBuilder, UnionType,
|
||||
},
|
||||
Db, KnownModule, Program,
|
||||
Db, FxOrderSet, KnownModule, Program,
|
||||
};
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools as _;
|
||||
|
@ -167,13 +168,25 @@ impl<'db> GenericAlias<'db> {
|
|||
self.origin(db).definition(db)
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||
pub(super) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: TypeMapping<'a, 'db>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.origin(db),
|
||||
self.specialization(db).apply_type_mapping(db, type_mapping),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
self.specialization(db).find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<GenericAlias<'db>> for Type<'db> {
|
||||
|
@ -262,6 +275,17 @@ impl<'db> ClassType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
match self {
|
||||
Self::NonGeneric(_) => {}
|
||||
Self::Generic(generic) => generic.find_legacy_typevars(db, typevars),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the [method resolution order] ("MRO") of the class.
|
||||
///
|
||||
/// If the MRO could not be accurately resolved, this method falls back to iterating
|
||||
|
|
|
@ -4,8 +4,8 @@ use super::protocol_class::ProtocolInterface;
|
|||
use super::{ClassType, KnownClass, SubclassOfType, Type};
|
||||
use crate::symbol::{Symbol, SymbolAndQualifiers};
|
||||
use crate::types::generics::TypeMapping;
|
||||
use crate::types::ClassLiteral;
|
||||
use crate::Db;
|
||||
use crate::types::{ClassLiteral, TypeVarInstance};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
||||
|
||||
|
@ -132,6 +132,14 @@ impl<'db> NominalInstanceType<'db> {
|
|||
class: self.class.apply_type_mapping(db, type_mapping),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
self.class.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
|
||||
|
@ -270,7 +278,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_specialization<'a>(
|
||||
pub(super) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: TypeMapping<'a, 'db>,
|
||||
|
@ -284,6 +292,21 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
match self.0 {
|
||||
Protocol::FromClass(class) => {
|
||||
class.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
Protocol::Synthesized(synthesized) => {
|
||||
synthesized.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration of the two kinds of protocol types: those that originate from a class
|
||||
|
@ -310,9 +333,10 @@ impl<'db> Protocol<'db> {
|
|||
}
|
||||
|
||||
mod synthesized_protocol {
|
||||
use crate::db::Db;
|
||||
use crate::types::generics::TypeMapping;
|
||||
use crate::types::protocol_class::ProtocolInterface;
|
||||
use crate::types::TypeVarInstance;
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
/// A "synthesized" protocol type that is dissociated from a class definition in source code.
|
||||
///
|
||||
|
@ -339,6 +363,14 @@ mod synthesized_protocol {
|
|||
Self(self.0.specialized_and_normalized(db, type_mapping))
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
self.0.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
|
||||
pub(in crate::types) fn interface(self) -> ProtocolInterface<'db> {
|
||||
self.0
|
||||
}
|
||||
|
|
|
@ -5,10 +5,12 @@ use itertools::{Either, Itertools};
|
|||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::{
|
||||
db::Db,
|
||||
semantic_index::{symbol_table, use_def_map},
|
||||
symbol::{symbol_from_bindings, symbol_from_declarations},
|
||||
types::{ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance,
|
||||
},
|
||||
{Db, FxOrderSet},
|
||||
};
|
||||
|
||||
impl<'db> ClassLiteral<'db> {
|
||||
|
@ -188,6 +190,21 @@ impl<'db> ProtocolInterface<'db> {
|
|||
Self::SelfReference => Self::SelfReference,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
match self {
|
||||
Self::Members(members) => {
|
||||
for data in members.inner(db).values() {
|
||||
data.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
Self::SelfReference => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, salsa::Update)]
|
||||
|
@ -210,6 +227,14 @@ impl<'db> ProtocolMemberData<'db> {
|
|||
qualifiers: self.qualifiers,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_legacy_typevars(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
self.ty.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
|
||||
/// A single member of a protocol interface.
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use crate::symbol::SymbolAndQualifiers;
|
||||
use crate::types::generics::TypeMapping;
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
use super::{ClassType, Db, DynamicType, KnownClass, MemberLookupPolicy, Type};
|
||||
use super::{ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeVarInstance};
|
||||
|
||||
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
|
@ -66,6 +68,32 @@ impl<'db> SubclassOfType<'db> {
|
|||
!self.is_dynamic()
|
||||
}
|
||||
|
||||
pub(super) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: TypeMapping<'a, 'db>,
|
||||
) -> Self {
|
||||
match self.subclass_of {
|
||||
SubclassOfInner::Class(class) => Self {
|
||||
subclass_of: SubclassOfInner::Class(class.apply_type_mapping(db, type_mapping)),
|
||||
},
|
||||
SubclassOfInner::Dynamic(_) => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
match self.subclass_of {
|
||||
SubclassOfInner::Class(class) => {
|
||||
class.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
SubclassOfInner::Dynamic(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn find_name_in_mro_with_policy(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue