[ty] Patch Self for fallback-methods on NamedTuples and TypedDicts (#20328)
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 / mkdocs (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 / 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

We use classes like
[`_typeshed._type_checker_internals.NamedTupleFallback`](d9c76e1d9f/stdlib/_typeshed/_type_checker_internals.pyi (L54-L75))
to tack on additional attributes/methods to instances of user-defined
`NamedTuple`s (or `TypedDict`s), even though these classes are not
present in the MRO of those types.

The problem is that those classes use implicit and explicit `Self`
annotations which refer to `NamedTupleFallback` itself, instead of to
the actual type that we're adding those methods to:
```py
class NamedTupleFallback(tuple[Any, ...]):
    # […]
    def _replace(self, **kwargs: Any) -> typing_extensions.Self: ...
```

In effect, when we access `_replace` on an instance of a custom
`NamedTuple` instance, its `self` parameter and return type refer to the
wrong `Self`. This leads to incorrect *"Argument to bound method
`_replace` is incorrect: Argument type `Person` does not satisfy upper
bound `NamedTupleFallback` of type variable `Self`"* errors on #18007.
It would also lead to similar errors on `TypedDict`s, if they would
already implement assignability properly.


## Test Plan

I applied the following patch to typeshed and verified that no errors
appear anymore.

<details>

```diff
diff --git a/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi
index feb22aae00..8e41034f19 100644
--- a/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi
+++ b/crates/ty_vendored/vendor/typeshed/stdlib/_typeshed/_type_checker_internals.pyi
@@ -29,27 +29,27 @@ class TypedDictFallback(Mapping[str, object], metaclass=ABCMeta):
         __readonly_keys__: ClassVar[frozenset[str]]
         __mutable_keys__: ClassVar[frozenset[str]]
 
-    def copy(self) -> typing_extensions.Self: ...
+    def copy(self: typing_extensions.Self) -> typing_extensions.Self: ...
     # Using Never so that only calls using mypy plugin hook that specialize the signature
     # can go through.
-    def setdefault(self, k: Never, default: object) -> object: ...
+    def setdefault(self: typing_extensions.Self, k: Never, default: object) -> object: ...
     # Mypy plugin hook for 'pop' expects that 'default' has a type variable type.
-    def pop(self, k: Never, default: _T = ...) -> object: ...  # pyright: ignore[reportInvalidTypeVarUse]
-    def update(self, m: typing_extensions.Self, /) -> None: ...
-    def __delitem__(self, k: Never) -> None: ...
-    def items(self) -> dict_items[str, object]: ...
-    def keys(self) -> dict_keys[str, object]: ...
-    def values(self) -> dict_values[str, object]: ...
+    def pop(self: typing_extensions.Self, k: Never, default: _T = ...) -> object: ...  # pyright: ignore[reportInvalidTypeVarUse]
+    def update(self: typing_extensions.Self, m: typing_extensions.Self, /) -> None: ...
+    def __delitem__(self: typing_extensions.Self, k: Never) -> None: ...
+    def items(self: typing_extensions.Self) -> dict_items[str, object]: ...
+    def keys(self: typing_extensions.Self) -> dict_keys[str, object]: ...
+    def values(self: typing_extensions.Self) -> dict_values[str, object]: ...
     @overload
-    def __or__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ...
+    def __or__(self: typing_extensions.Self, value: typing_extensions.Self, /) -> typing_extensions.Self: ...
     @overload
-    def __or__(self, value: dict[str, Any], /) -> dict[str, object]: ...
+    def __or__(self: typing_extensions.Self, value: dict[str, Any], /) -> dict[str, object]: ...
     @overload
-    def __ror__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ...
+    def __ror__(self: typing_extensions.Self, value: typing_extensions.Self, /) -> typing_extensions.Self: ...
     @overload
-    def __ror__(self, value: dict[str, Any], /) -> dict[str, object]: ...
+    def __ror__(self: typing_extensions.Self, value: dict[str, Any], /) -> dict[str, object]: ...
     # supposedly incompatible definitions of __or__ and __ior__
-    def __ior__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ...  # type: ignore[misc]
+    def __ior__(self: typing_extensions.Self, value: typing_extensions.Self, /) -> typing_extensions.Self: ...  # type: ignore[misc]
 
 # Fallback type providing methods and attributes that appear on all `NamedTuple` types.
 class NamedTupleFallback(tuple[Any, ...]):
@@ -61,18 +61,18 @@ class NamedTupleFallback(tuple[Any, ...]):
         __orig_bases__: ClassVar[tuple[Any, ...]]
 
     @overload
-    def __init__(self, typename: str, fields: Iterable[tuple[str, Any]], /) -> None: ...
+    def __init__(self: typing_extensions.Self, typename: str, fields: Iterable[tuple[str, Any]], /) -> None: ...
     @overload
     @typing_extensions.deprecated(
         "Creating a typing.NamedTuple using keyword arguments is deprecated and support will be removed in Python 3.15"
     )
-    def __init__(self, typename: str, fields: None = None, /, **kwargs: Any) -> None: ...
+    def __init__(self: typing_extensions.Self, typename: str, fields: None = None, /, **kwargs: Any) -> None: ...
     @classmethod
     def _make(cls, iterable: Iterable[Any]) -> typing_extensions.Self: ...
-    def _asdict(self) -> dict[str, Any]: ...
-    def _replace(self, **kwargs: Any) -> typing_extensions.Self: ...
+    def _asdict(self: typing_extensions.Self) -> dict[str, Any]: ...
+    def _replace(self: typing_extensions.Self, **kwargs: Any) -> typing_extensions.Self: ...
     if sys.version_info >= (3, 13):
-        def __replace__(self, **kwargs: Any) -> typing_extensions.Self: ...
+        def __replace__(self: typing_extensions.Self, **kwargs: Any) -> typing_extensions.Self: ...
 
 # Non-default variations to accommodate couroutines, and `AwaitableGenerator` having a 4th type parameter.
 _S = TypeVar("_S")
```

</details>
This commit is contained in:
David Peter 2025-09-15 16:21:53 +02:00 committed by GitHub
parent 9a9ebc316c
commit 25cbf38a47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 188 additions and 7 deletions

View file

@ -272,16 +272,34 @@ reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(itera
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace
# TODO: should be `Person` once we support `Self` # TODO: should be `Person` once we support implicit type of `self`
reveal_type(Person._make(("Alice", 42))) # revealed: Unknown reveal_type(Person._make(("Alice", 42))) # revealed: Unknown
person = Person("Alice", 42) person = Person("Alice", 42)
reveal_type(person._asdict()) # revealed: dict[str, Any] reveal_type(person._asdict()) # revealed: dict[str, Any]
# TODO: should be `Person` once we support `Self` # TODO: should be `Person` once we support implicit type of `self`
reveal_type(person._replace(name="Bob")) # revealed: Unknown reveal_type(person._replace(name="Bob")) # revealed: Unknown
``` ```
When accessing them on child classes of generic `NamedTuple`s, the return type is specialized
accordingly:
```py
from typing import NamedTuple, Generic, TypeVar
T = TypeVar("T")
class Box(NamedTuple, Generic[T]):
content: T
class IntBox(Box[int]):
pass
# TODO: should be `IntBox` once we support the implicit type of `self`
reveal_type(IntBox(1)._replace(content=42)) # revealed: Unknown
```
## `collections.namedtuple` ## `collections.namedtuple`
```py ```py

View file

@ -627,6 +627,18 @@ alice: Employee = {"name": "Alice", "employee_id": 1}
# error: [missing-typed-dict-key] "Missing required key 'employee_id' in TypedDict `Employee` constructor" # error: [missing-typed-dict-key] "Missing required key 'employee_id' in TypedDict `Employee` constructor"
eve: Employee = {"name": "Eve"} eve: Employee = {"name": "Eve"}
def combine(p: Person, e: Employee):
# TODO: Should be `Person` once we support the implicit type of self
reveal_type(p.copy()) # revealed: Unknown
# TODO: Should be `Employee` once we support the implicit type of self
reveal_type(e.copy()) # revealed: Unknown
reveal_type(p | p) # revealed: Person
reveal_type(e | e) # revealed: Employee
# TODO: Should be `Person` once we support the implicit type of self and subtyping for TypedDicts
reveal_type(p | e) # revealed: Employee
``` ```
When inheriting from a `TypedDict` with a different `total` setting, inherited fields maintain their When inheriting from a `TypedDict` with a different `total` setting, inherited fields maintain their

View file

@ -5971,6 +5971,19 @@ impl<'db> Type<'db> {
self self
} }
} }
TypeMapping::ReplaceSelf { new_upper_bound } => {
if bound_typevar.typevar(db).is_self(db) {
Type::TypeVar(
BoundTypeVarInstance::synthetic_self(
db,
*new_upper_bound,
bound_typevar.binding_context(db)
)
)
} else {
self
}
}
TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) | TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) |
TypeMapping::MarkTypeVarsInferable(_) => self, TypeMapping::MarkTypeVarsInferable(_) => self,
TypeMapping::Materialize(materialization_kind) => { TypeMapping::Materialize(materialization_kind) => {
@ -5994,7 +6007,8 @@ impl<'db> Type<'db> {
} }
TypeMapping::PromoteLiterals | TypeMapping::PromoteLiterals |
TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindLegacyTypevars(_) |
TypeMapping::BindSelf(_) TypeMapping::BindSelf(_) |
TypeMapping::ReplaceSelf { .. }
=> self, => self,
TypeMapping::Materialize(materialization_kind) => Type::NonInferableTypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor)) TypeMapping::Materialize(materialization_kind) => Type::NonInferableTypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
@ -6008,6 +6022,7 @@ impl<'db> Type<'db> {
TypeMapping::PartialSpecialization(_) | TypeMapping::PartialSpecialization(_) |
TypeMapping::PromoteLiterals | TypeMapping::PromoteLiterals |
TypeMapping::BindSelf(_) | TypeMapping::BindSelf(_) |
TypeMapping::ReplaceSelf { .. } |
TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::MarkTypeVarsInferable(_) |
TypeMapping::Materialize(_) => self, TypeMapping::Materialize(_) => self,
} }
@ -6116,6 +6131,7 @@ impl<'db> Type<'db> {
TypeMapping::PartialSpecialization(_) | TypeMapping::PartialSpecialization(_) |
TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindLegacyTypevars(_) |
TypeMapping::BindSelf(_) | TypeMapping::BindSelf(_) |
TypeMapping::ReplaceSelf { .. } |
TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::MarkTypeVarsInferable(_) |
TypeMapping::Materialize(_) => self, TypeMapping::Materialize(_) => self,
TypeMapping::PromoteLiterals => self.literal_fallback_instance(db) TypeMapping::PromoteLiterals => self.literal_fallback_instance(db)
@ -6127,6 +6143,7 @@ impl<'db> Type<'db> {
TypeMapping::PartialSpecialization(_) | TypeMapping::PartialSpecialization(_) |
TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindLegacyTypevars(_) |
TypeMapping::BindSelf(_) | TypeMapping::BindSelf(_) |
TypeMapping::ReplaceSelf { .. } |
TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::MarkTypeVarsInferable(_) |
TypeMapping::PromoteLiterals => self, TypeMapping::PromoteLiterals => self,
TypeMapping::Materialize(materialization_kind) => match materialization_kind { TypeMapping::Materialize(materialization_kind) => match materialization_kind {
@ -6662,6 +6679,8 @@ pub enum TypeMapping<'a, 'db> {
BindLegacyTypevars(BindingContext<'db>), BindLegacyTypevars(BindingContext<'db>),
/// Binds any `typing.Self` typevar with a particular `self` class. /// Binds any `typing.Self` typevar with a particular `self` class.
BindSelf(Type<'db>), BindSelf(Type<'db>),
/// Replaces occurrences of `typing.Self` with a new `Self` type variable with the given upper bound.
ReplaceSelf { new_upper_bound: Type<'db> },
/// Marks the typevars that are bound by a generic class or function as inferable. /// Marks the typevars that are bound by a generic class or function as inferable.
MarkTypeVarsInferable(BindingContext<'db>), MarkTypeVarsInferable(BindingContext<'db>),
/// Create the top or bottom materialization of a type. /// Create the top or bottom materialization of a type.
@ -6683,6 +6702,9 @@ fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
TypeMapping::BindSelf(self_type) => { TypeMapping::BindSelf(self_type) => {
visitor.visit_type(db, *self_type); visitor.visit_type(db, *self_type);
} }
TypeMapping::ReplaceSelf { new_upper_bound } => {
visitor.visit_type(db, *new_upper_bound);
}
TypeMapping::PromoteLiterals TypeMapping::PromoteLiterals
| TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindLegacyTypevars(_)
| TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::MarkTypeVarsInferable(_)
@ -6704,6 +6726,9 @@ impl<'db> TypeMapping<'_, 'db> {
TypeMapping::BindLegacyTypevars(*binding_context) TypeMapping::BindLegacyTypevars(*binding_context)
} }
TypeMapping::BindSelf(self_type) => TypeMapping::BindSelf(*self_type), TypeMapping::BindSelf(self_type) => TypeMapping::BindSelf(*self_type),
TypeMapping::ReplaceSelf { new_upper_bound } => TypeMapping::ReplaceSelf {
new_upper_bound: *new_upper_bound,
},
TypeMapping::MarkTypeVarsInferable(binding_context) => { TypeMapping::MarkTypeVarsInferable(binding_context) => {
TypeMapping::MarkTypeVarsInferable(*binding_context) TypeMapping::MarkTypeVarsInferable(*binding_context)
} }
@ -6728,6 +6753,9 @@ impl<'db> TypeMapping<'_, 'db> {
TypeMapping::BindSelf(self_type) => { TypeMapping::BindSelf(self_type) => {
TypeMapping::BindSelf(self_type.normalized_impl(db, visitor)) TypeMapping::BindSelf(self_type.normalized_impl(db, visitor))
} }
TypeMapping::ReplaceSelf { new_upper_bound } => TypeMapping::ReplaceSelf {
new_upper_bound: new_upper_bound.normalized_impl(db, visitor),
},
TypeMapping::MarkTypeVarsInferable(binding_context) => { TypeMapping::MarkTypeVarsInferable(binding_context) => {
TypeMapping::MarkTypeVarsInferable(*binding_context) TypeMapping::MarkTypeVarsInferable(*binding_context)
} }
@ -6736,6 +6764,37 @@ impl<'db> TypeMapping<'_, 'db> {
} }
} }
} }
/// Update the generic context of a [`Signature`] according to the current type mapping
pub(crate) fn update_signature_generic_context(
&self,
db: &'db dyn Db,
context: GenericContext<'db>,
) -> GenericContext<'db> {
match self {
TypeMapping::Specialization(_)
| TypeMapping::PartialSpecialization(_)
| TypeMapping::PromoteLiterals
| TypeMapping::BindLegacyTypevars(_)
| TypeMapping::MarkTypeVarsInferable(_)
| TypeMapping::Materialize(_)
| TypeMapping::BindSelf(_) => context,
TypeMapping::ReplaceSelf { new_upper_bound } => GenericContext::from_typevar_instances(
db,
context.variables(db).iter().map(|typevar| {
if typevar.typevar(db).is_self(db) {
BoundTypeVarInstance::synthetic_self(
db,
*new_upper_bound,
typevar.binding_context(db),
)
} else {
*typevar
}
}),
),
}
}
} }
/// A Salsa-tracked constraint set. This is only needed to have something appropriately small to /// A Salsa-tracked constraint set. This is only needed to have something appropriately small to
@ -7663,6 +7722,27 @@ impl<'db> BoundTypeVarInstance<'db> {
) )
} }
/// Create a new synthetic `Self` type variable with the given upper bound.
pub(crate) fn synthetic_self(
db: &'db dyn Db,
upper_bound: Type<'db>,
binding_context: BindingContext<'db>,
) -> Self {
Self::new(
db,
TypeVarInstance::new(
db,
Name::new_static("Self"),
None,
Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()),
Some(TypeVarVariance::Invariant),
None,
TypeVarKind::TypingSelf,
),
binding_context,
)
}
pub(crate) fn variance_with_polarity( pub(crate) fn variance_with_polarity(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
@ -10836,6 +10916,24 @@ impl<'db> TypeIsType<'db> {
} }
} }
/// Walk the MRO of this class and return the last class just before the specified known base.
/// This can be used to determine upper bounds for `Self` type variables on methods that are
/// being added to the given class.
pub(super) fn determine_upper_bound<'db>(
db: &'db dyn Db,
class_literal: ClassLiteral<'db>,
specialization: Option<Specialization<'db>>,
is_known_base: impl Fn(ClassBase<'db>) -> bool,
) -> Type<'db> {
let upper_bound = class_literal
.iter_mro(db, specialization)
.take_while(|base| !is_known_base(*base))
.filter_map(ClassBase::into_class)
.last()
.unwrap_or_else(|| class_literal.unknown_specialization(db));
Type::instance(db, upper_bound)
}
// Make sure that the `Type` enum does not grow unexpectedly. // Make sure that the `Type` enum does not grow unexpectedly.
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]

View file

@ -30,7 +30,8 @@ use crate::types::{
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, infer_definition_types, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, determine_upper_bound,
infer_definition_types,
}; };
use crate::{ use crate::{
Db, FxIndexMap, FxOrderSet, Program, Db, FxIndexMap, FxOrderSet, Program,
@ -1975,7 +1976,20 @@ impl<'db> ClassLiteral<'db> {
return KnownClass::TypedDictFallback return KnownClass::TypedDictFallback
.to_class_literal(db) .to_class_literal(db)
.find_name_in_mro_with_policy(db, name, policy) .find_name_in_mro_with_policy(db, name, policy)
.expect("Will return Some() when called on class literal"); .expect("Will return Some() when called on class literal")
.map_type(|ty| {
ty.apply_type_mapping(
db,
&TypeMapping::ReplaceSelf {
new_upper_bound: determine_upper_bound(
db,
self,
None,
ClassBase::is_typed_dict,
),
},
)
});
} }
} }
if lookup_result.is_ok() { if lookup_result.is_ok() {
@ -2256,6 +2270,22 @@ impl<'db> ClassLiteral<'db> {
.own_class_member(db, self.generic_context(db), None, name) .own_class_member(db, self.generic_context(db), None, name)
.place .place
.ignore_possibly_unbound() .ignore_possibly_unbound()
.map(|ty| {
ty.apply_type_mapping(
db,
&TypeMapping::ReplaceSelf {
new_upper_bound: determine_upper_bound(
db,
self,
specialization,
|base| {
base.into_class()
.is_some_and(|c| c.is_known(db, KnownClass::Tuple))
},
),
},
)
})
} }
(CodeGeneratorKind::DataclassLike, "__replace__") (CodeGeneratorKind::DataclassLike, "__replace__")
if Program::get(db).python_version(db) >= PythonVersion::PY313 => if Program::get(db).python_version(db) >= PythonVersion::PY313 =>
@ -2578,6 +2608,12 @@ impl<'db> ClassLiteral<'db> {
.to_class_literal(db) .to_class_literal(db)
.find_name_in_mro_with_policy(db, name, policy) .find_name_in_mro_with_policy(db, name, policy)
.expect("`find_name_in_mro_with_policy` will return `Some()` when called on class literal") .expect("`find_name_in_mro_with_policy` will return `Some()` when called on class literal")
.map_type(|ty|
ty.apply_type_mapping(
db,
&TypeMapping::ReplaceSelf {new_upper_bound: determine_upper_bound(db, self, specialization, ClassBase::is_typed_dict) }
)
)
} }
} }
@ -2815,7 +2851,18 @@ impl<'db> ClassLiteral<'db> {
ClassBase::TypedDict => { ClassBase::TypedDict => {
return KnownClass::TypedDictFallback return KnownClass::TypedDictFallback
.to_instance(db) .to_instance(db)
.instance_member(db, name); .instance_member(db, name)
.map_type(|ty| {
ty.apply_type_mapping(
db,
&TypeMapping::ReplaceSelf {
new_upper_bound: Type::instance(
db,
self.unknown_specialization(db),
),
},
)
});
} }
} }
} }

View file

@ -69,6 +69,10 @@ impl<'db> ClassBase<'db> {
.map_or(Self::unknown(), Self::Class) .map_or(Self::unknown(), Self::Class)
} }
pub(super) const fn is_typed_dict(self) -> bool {
matches!(self, ClassBase::TypedDict)
}
/// Attempt to resolve `ty` into a `ClassBase`. /// Attempt to resolve `ty` into a `ClassBase`.
/// ///
/// Return `None` if `ty` is not an acceptable type for a class base. /// Return `None` if `ty` is not an acceptable type for a class base.

View file

@ -470,7 +470,9 @@ impl<'db> Signature<'db> {
_ => type_mapping, _ => type_mapping,
}; };
Self { Self {
generic_context: self.generic_context, generic_context: self
.generic_context
.map(|context| type_mapping.update_signature_generic_context(db, context)),
inherited_generic_context: self.inherited_generic_context, inherited_generic_context: self.inherited_generic_context,
definition: self.definition, definition: self.definition,
parameters: self parameters: self