From d29324e35ff691aa6307645f2fce56b0c62a030c Mon Sep 17 00:00:00 2001 From: iliya Date: Thu, 22 Oct 2026 15:47:01 +0300 Subject: [PATCH] perf(checker): reuse this-independent interface members Avoid repeated synthetic-this member instantiation for proved completed nongeneric interfaces. Preserve captured and polymorphic this, generic interfaces, and unresolved bases. Refs microsoft/TypeScript#63555 --- tsc/internal/checker/checker.go | 34 +- tsc/internal/checker/types.go | 2 + .../thisIndependentInterfaceMembers.js | 253 +++++++ .../thisIndependentInterfaceMembers.symbols | 649 ++++++++++++++++++ .../thisIndependentInterfaceMembers.types | 493 +++++++++++++ .../thisIndependentInterfaceMembers.ts | 170 +++++ 6 files changed, 1600 insertions(+), 1 deletion(-) create mode 100644 tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.js create mode 100644 tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.types create mode 100644 tsc/testdata/tests/cases/compiler/thisIndependentInterfaceMembers.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 10ddb3c0c88b4..bc9a8549595f7 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -19549,6 +19549,13 @@ func (c *Checker) resolveObjectTypeMembers(t *Type, source *Type, typeParameters var constructSignatures []*Signature var indexInfos []*IndexInfo var instantiated bool + thislessMembers := source.objectFlags&ObjectFlagsClassOrInterface == ObjectFlagsInterface && + source.symbol.Flags()&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) == ast.SymbolFlagsInterface && + len(source.AsInterfaceType().TypeParameters()) == 0 && + len(source.symbol.Declarations()) != 0 && + core.Every(source.symbol.Declarations(), func(d *ast.Node) bool { + return ast.IsInterfaceDeclaration(d) && d.Flags&ast.NodeFlagsContainsThis == 0 + }) resolved := c.resolveDeclaredMembers(source) if slices.Equal(typeParameters, typeArguments) { members = resolved.declaredMembers @@ -19586,9 +19593,33 @@ func (c *Checker) resolveObjectTypeMembers(t *Type, source *Type, typeParameters indexInfos = core.Concatenate(indexInfos, core.Filter(inheritedIndexInfos, func(info *IndexInfo) bool { return findIndexInfo(indexInfos, info.keyType) == nil })) + thislessMembers = thislessMembers && c.isThislessResolvedBase(baseType, instantiatedBaseType) } } c.setStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos) + if thislessMembers && source.AsInterfaceType().baseTypesResolved { + source.AsInterfaceType().thislessMembers = true + } +} + +// Prove this independence only from completed bases. In particular, mapped types +// publish an empty member table during resolution; that placeholder is not a proof. +func (c *Checker) isThislessResolvedBase(base *Type, resolved *Type) bool { + if resolved.objectFlags&ObjectFlagsMembersResolved == 0 { + return false + } + if base.objectFlags&ObjectFlagsMapped != 0 { + return base == resolved && base.AsMappedType().membersFinalized && !base.AsMappedType().containsError && + !c.couldContainTypeVariables(base) && + (base.alias == nil || !core.Some(base.alias.typeArguments, c.couldContainTypeVariables)) + } + if base.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + target := getTargetType(base) + return target.objectFlags&ObjectFlagsClassOrInterface == ObjectFlagsInterface && + target.AsInterfaceType().baseTypesResolved && len(target.AsInterfaceType().TypeParameters()) == 0 && + (target.AsInterfaceType().thisType == nil || target.AsInterfaceType().thislessMembers) + } + return false } func findIndexInfo(indexInfos []*IndexInfo, keyType *Type) *IndexInfo { @@ -20011,7 +20042,7 @@ func (c *Checker) getTypeWithThisArgument(t *Type, thisArgument *Type, needAppar target := t.Target() typeArguments := c.getTypeArguments(t) if len(target.AsInterfaceType().TypeParameters()) == len(typeArguments) { - if thisArgument == nil { + if thisArgument == nil || target.AsInterfaceType().thislessMembers { thisArgument = target.AsInterfaceType().thisType } return c.createTypeReference(target, core.Concatenate(typeArguments, []*Type{thisArgument})) @@ -21420,6 +21451,7 @@ func (c *Checker) resolveMappedTypeMembers(t *Type) { forEachType(c.getLowerBoundOfKeyType(constraintType), addMemberForKeyType) } c.setStructuredTypeMembers(t, members, nil, nil, indexInfos) + t.AsMappedType().membersFinalized = true } func (c *Checker) getTypeOfMappedSymbol(symbol *ast.Symbol) *Type { diff --git a/tsc/internal/checker/types.go b/tsc/internal/checker/types.go index a3cfe28c2e735..33f0261eb0f9b 100644 --- a/tsc/internal/checker/types.go +++ b/tsc/internal/checker/types.go @@ -1227,6 +1227,7 @@ type InterfaceType struct { allTypeParameters []*Type // Type parameters (outer + local + thisType) outerTypeParameterCount int // Count of outer type parameters thisType *Type // The "this" type (nil if none) + thislessMembers bool // Completed non-generic interface members are unaffected by a this argument baseTypesResolved bool declaredMembersResolved bool resolvedBaseConstructorType *Type @@ -1326,6 +1327,7 @@ type MappedType struct { templateType *Type modifiersType *Type resolvedApparentType *Type + membersFinalized bool containsError bool } diff --git a/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.js b/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.js new file mode 100644 index 0000000000000..2a335cb0a3a19 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.js @@ -0,0 +1,253 @@ +//// [tests/cases/compiler/thisIndependentInterfaceMembers.ts] //// + +//// [thisIndependentInterfaceMembers.ts] +// The member reuse optimization must preserve real, outer and foreign `this`. +namespace capturedOuterThis { + class Owner { + read() { + type OuterMap = { readonly [K in "owner"]: this }; + interface Base extends OuterMap {} + interface Derived extends Base { readonly derived: "derived" } + const value = null! as Derived; + const exact: this = value.owner; + // @ts-expect-error The owner is not the mapped property's interface. + const wrong: Derived = value.owner; + return exact; + } + } + class SpecialOwner extends Owner { readonly special = "special" } + declare const owner: SpecialOwner; + const exactOwner: SpecialOwner = owner.read(); +} + +namespace fluentAlias { + interface Fluent { self(): this } + type FluentAlias = Fluent; + type Marker = { readonly [K in "marker"]: "marker" }; + interface Mixed extends Marker, FluentAlias {} + interface Derived extends Mixed { readonly derived: "derived" } + declare const value: Derived; + const exact: Derived = value.self(); +} + +namespace foreignMappedThis { + interface Foreign { self(): this; readonly foreign: "foreign" } + type Copy = { readonly [K in keyof T]: T[K] }; + interface Copied extends Copy {} + interface Derived extends Copied { readonly derived: "derived" } + declare const value: Derived; + const exact: Foreign = value.self(); + // @ts-expect-error A concrete map does not rebind the foreign method's this. + const wrong: Derived = value.self(); +} + +namespace mergedDeclarations { + type Marker = { readonly [K in "marker"]: "marker" }; + interface Merged extends Marker {} + interface Merged { self(): this } + interface Derived extends Merged { readonly derived: "derived" } + declare const value: Derived; + const exact: Derived = value.self(); + const marker: "marker" = value.marker; + + interface Plain extends Marker {} + interface Plain { readonly merged: "merged" } + interface PlainDerived extends Plain { readonly derived: "derived" } + declare const plain: PlainDerived; + const inherited: "merged" = plain.merged; +} + +namespace genericInterfaces { + type Marker = { readonly [K in "value"]: T }; + interface Generic extends Marker { self(): this } + interface NumberValue extends Generic { readonly numeric: "numeric" } + interface StringValue extends Generic { readonly textual: "textual" } + declare const numeric: NumberValue; + declare const textual: StringValue; + const numberValue: number = numeric.value; + const stringValue: string = textual.value; + const exact: NumberValue = numeric.self(); + // @ts-expect-error Distinct type arguments may not share member substitutions. + const wrong: number = textual.value; + + function enclosing(input: T) { + type OuterMap = { readonly [K in "value"]: T }; + interface Local extends OuterMap {} + interface Derived extends Local { readonly local: "local" } + const value = null! as Derived; + const exact: T = value.value; + return { input, exact }; + } + const numericOuter: number = enclosing(1).exact; + const stringOuter: string = enclosing("text").exact; + // @ts-expect-error An enclosing generic remains specific to the invocation. + const wrongOuter: number = enclosing("text").exact; +} + +namespace mappedAliasArguments { + type Value = { readonly [K in "value"]: T }; + type Alias = Value; + interface Generic extends Alias {} + interface NumberValue extends Generic {} + interface StringValue extends Generic {} + declare const numeric: NumberValue; + declare const textual: StringValue; + const numberValue: number = numeric.value; + const stringValue: string = textual.value; + // @ts-expect-error A mapped alias must retain its variable-containing argument. + const wrong: number = textual.value; + + interface Foreign { self(): this; readonly foreign: "foreign" } + type Copy = { readonly [K in keyof T]: T[K] }; + type ConcreteAlias = Copy; + interface Copied extends ConcreteAlias {} + interface Derived extends Copied { readonly derived: "derived" } + declare const derived: Derived; + const foreign: Foreign = derived.self(); + // @ts-expect-error Mapping must preserve foreign-this even behind an alias. + const notDerived: Derived = derived.self(); +} + +namespace recursiveMembers { + type Links = { readonly [K in "next"]: Node }; + interface Node extends Links { readonly value: number } + interface Derived extends Node { readonly own: string } + declare const value: Derived; + const next: Node = value.next; + const nested: number = value.next.next.value; + // @ts-expect-error Recursive members retain the declared Node, not Derived. + const wrong: Derived = value.next; + + // This forces lookup of a mapped member through a recursively referenced type. + type Reenter = { readonly [K in "item"]: T }; + interface Recursive extends Reenter { readonly own: "own" } + interface RecursiveDerived extends Recursive { readonly more: "more" } + declare const recursive: RecursiveDerived; + const own: "own" = recursive.item.item.own; + // @ts-expect-error A re-entrant lookup does not substitute a descendant. + const wrongRecursive: RecursiveDerived = recursive.item; +} + +namespace rejectedBases { + type LeftMap = { readonly [K in "value"]: number }; + type RightMap = { readonly [K in "value"]: string }; + interface Left extends LeftMap {} + interface Right extends RightMap {} + // @ts-expect-error Conflicting inherited properties must still reject. + interface Conflict extends Left, Right {} + + // @ts-expect-error A circular mapped constraint remains an error. + type Reentered = { [K in keyof Circular]: Circular[K] }; + // @ts-expect-error Resolution placeholders must not prove valid ancestry. + interface Circular extends Reentered { readonly own: string } + + // @ts-expect-error An unresolved mapped key still reports its error. + type ErrorMap = { [K in MissingKey]: number }; + interface ErrorBase extends ErrorMap {} + // @ts-expect-error The erroneous key must not erase the numeric index contract. + interface ErrorDerived extends ErrorBase { readonly own: "own" } +} + +namespace concreteMappedMembers { + type Value = { readonly [K in "value"]: T }; + interface Plain extends Value {} + interface Derived extends Plain { readonly own: "own" } + declare const value: Derived; + const exact: number = value.value; + // @ts-expect-error Concrete mapped properties keep their exact type. + const wrong: string = value.value; + + interface WithIndex { readonly [key: string]: number } + interface IndexBase extends WithIndex {} + interface IndexDerived extends IndexBase { readonly own: 1 } + declare const indexed: IndexDerived; + const entry: number = indexed["arbitrary"]; + + type Callable = { (): number; readonly [K: string]: unknown }; + interface CallBase extends Callable {} + interface CallDerived extends CallBase { readonly own: "own" } + declare const callable: CallDerived; + const result: number = callable(); +} + + +//// [thisIndependentInterfaceMembers.js] +"use strict"; +// The member reuse optimization must preserve real, outer and foreign `this`. +var capturedOuterThis; +(function (capturedOuterThis) { + class Owner { + read() { + const value = null; + const exact = value.owner; + // @ts-expect-error The owner is not the mapped property's interface. + const wrong = value.owner; + return exact; + } + } + class SpecialOwner extends Owner { + special = "special"; + } + const exactOwner = owner.read(); +})(capturedOuterThis || (capturedOuterThis = {})); +var fluentAlias; +(function (fluentAlias) { + const exact = value.self(); +})(fluentAlias || (fluentAlias = {})); +var foreignMappedThis; +(function (foreignMappedThis) { + const exact = value.self(); + // @ts-expect-error A concrete map does not rebind the foreign method's this. + const wrong = value.self(); +})(foreignMappedThis || (foreignMappedThis = {})); +var mergedDeclarations; +(function (mergedDeclarations) { + const exact = value.self(); + const marker = value.marker; + const inherited = plain.merged; +})(mergedDeclarations || (mergedDeclarations = {})); +var genericInterfaces; +(function (genericInterfaces) { + const numberValue = numeric.value; + const stringValue = textual.value; + const exact = numeric.self(); + // @ts-expect-error Distinct type arguments may not share member substitutions. + const wrong = textual.value; + function enclosing(input) { + const value = null; + const exact = value.value; + return { input, exact }; + } + const numericOuter = enclosing(1).exact; + const stringOuter = enclosing("text").exact; + // @ts-expect-error An enclosing generic remains specific to the invocation. + const wrongOuter = enclosing("text").exact; +})(genericInterfaces || (genericInterfaces = {})); +var mappedAliasArguments; +(function (mappedAliasArguments) { + const numberValue = numeric.value; + const stringValue = textual.value; + // @ts-expect-error A mapped alias must retain its variable-containing argument. + const wrong = textual.value; + const foreign = derived.self(); + // @ts-expect-error Mapping must preserve foreign-this even behind an alias. + const notDerived = derived.self(); +})(mappedAliasArguments || (mappedAliasArguments = {})); +var recursiveMembers; +(function (recursiveMembers) { + const next = value.next; + const nested = value.next.next.value; + // @ts-expect-error Recursive members retain the declared Node, not Derived. + const wrong = value.next; + const own = recursive.item.item.own; + // @ts-expect-error A re-entrant lookup does not substitute a descendant. + const wrongRecursive = recursive.item; +})(recursiveMembers || (recursiveMembers = {})); +var concreteMappedMembers; +(function (concreteMappedMembers) { + const exact = value.value; + // @ts-expect-error Concrete mapped properties keep their exact type. + const wrong = value.value; + const entry = indexed["arbitrary"]; + const result = callable(); +})(concreteMappedMembers || (concreteMappedMembers = {})); diff --git a/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.symbols b/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.symbols new file mode 100644 index 0000000000000..58b188f0ad975 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.symbols @@ -0,0 +1,649 @@ +//// [tests/cases/compiler/thisIndependentInterfaceMembers.ts] //// + +=== thisIndependentInterfaceMembers.ts === +// The member reuse optimization must preserve real, outer and foreign `this`. +namespace capturedOuterThis { +>capturedOuterThis : Symbol(capturedOuterThis, Decl(thisIndependentInterfaceMembers.ts, 0, 0)) + + class Owner { +>Owner : Symbol(Owner, Decl(thisIndependentInterfaceMembers.ts, 1, 29)) + + read() { +>read : Symbol(Owner.read, Decl(thisIndependentInterfaceMembers.ts, 2, 17)) + + type OuterMap = { readonly [K in "owner"]: this }; +>OuterMap : Symbol(OuterMap, Decl(thisIndependentInterfaceMembers.ts, 3, 16)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 4, 40)) + + interface Base extends OuterMap {} +>Base : Symbol(Base, Decl(thisIndependentInterfaceMembers.ts, 4, 62)) +>OuterMap : Symbol(OuterMap, Decl(thisIndependentInterfaceMembers.ts, 3, 16)) + + interface Derived extends Base { readonly derived: "derived" } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 5, 46)) +>Base : Symbol(Base, Decl(thisIndependentInterfaceMembers.ts, 4, 62)) +>derived : Symbol(Derived.derived, Decl(thisIndependentInterfaceMembers.ts, 6, 44)) + + const value = null! as Derived; +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 7, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 5, 46)) + + const exact: this = value.owner; +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 8, 17)) +>value.owner : Symbol(owner) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 7, 17)) +>owner : Symbol(owner) + + // @ts-expect-error The owner is not the mapped property's interface. + const wrong: Derived = value.owner; +>wrong : Symbol(wrong, Decl(thisIndependentInterfaceMembers.ts, 10, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 5, 46)) +>value.owner : Symbol(owner) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 7, 17)) +>owner : Symbol(owner) + + return exact; +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 8, 17)) + } + } + class SpecialOwner extends Owner { readonly special = "special" } +>SpecialOwner : Symbol(SpecialOwner, Decl(thisIndependentInterfaceMembers.ts, 13, 5)) +>Owner : Symbol(Owner, Decl(thisIndependentInterfaceMembers.ts, 1, 29)) +>special : Symbol(SpecialOwner.special, Decl(thisIndependentInterfaceMembers.ts, 14, 38)) + + declare const owner: SpecialOwner; +>owner : Symbol(owner, Decl(thisIndependentInterfaceMembers.ts, 15, 17)) +>SpecialOwner : Symbol(SpecialOwner, Decl(thisIndependentInterfaceMembers.ts, 13, 5)) + + const exactOwner: SpecialOwner = owner.read(); +>exactOwner : Symbol(exactOwner, Decl(thisIndependentInterfaceMembers.ts, 16, 9)) +>SpecialOwner : Symbol(SpecialOwner, Decl(thisIndependentInterfaceMembers.ts, 13, 5)) +>owner.read : Symbol(Owner.read, Decl(thisIndependentInterfaceMembers.ts, 2, 17)) +>owner : Symbol(owner, Decl(thisIndependentInterfaceMembers.ts, 15, 17)) +>read : Symbol(Owner.read, Decl(thisIndependentInterfaceMembers.ts, 2, 17)) +} + +namespace fluentAlias { +>fluentAlias : Symbol(fluentAlias, Decl(thisIndependentInterfaceMembers.ts, 17, 1)) + + interface Fluent { self(): this } +>Fluent : Symbol(Fluent, Decl(thisIndependentInterfaceMembers.ts, 19, 23)) +>self : Symbol(Fluent.self, Decl(thisIndependentInterfaceMembers.ts, 20, 22)) + + type FluentAlias = Fluent; +>FluentAlias : Symbol(FluentAlias, Decl(thisIndependentInterfaceMembers.ts, 20, 37)) +>Fluent : Symbol(Fluent, Decl(thisIndependentInterfaceMembers.ts, 19, 23)) + + type Marker = { readonly [K in "marker"]: "marker" }; +>Marker : Symbol(Marker, Decl(thisIndependentInterfaceMembers.ts, 21, 30)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 22, 30)) + + interface Mixed extends Marker, FluentAlias {} +>Mixed : Symbol(Mixed, Decl(thisIndependentInterfaceMembers.ts, 22, 57)) +>Marker : Symbol(Marker, Decl(thisIndependentInterfaceMembers.ts, 21, 30)) +>FluentAlias : Symbol(FluentAlias, Decl(thisIndependentInterfaceMembers.ts, 20, 37)) + + interface Derived extends Mixed { readonly derived: "derived" } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 23, 50)) +>Mixed : Symbol(Mixed, Decl(thisIndependentInterfaceMembers.ts, 22, 57)) +>derived : Symbol(Derived.derived, Decl(thisIndependentInterfaceMembers.ts, 24, 37)) + + declare const value: Derived; +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 25, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 23, 50)) + + const exact: Derived = value.self(); +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 26, 9)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 23, 50)) +>value.self : Symbol(Fluent.self, Decl(thisIndependentInterfaceMembers.ts, 20, 22)) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 25, 17)) +>self : Symbol(Fluent.self, Decl(thisIndependentInterfaceMembers.ts, 20, 22)) +} + +namespace foreignMappedThis { +>foreignMappedThis : Symbol(foreignMappedThis, Decl(thisIndependentInterfaceMembers.ts, 27, 1)) + + interface Foreign { self(): this; readonly foreign: "foreign" } +>Foreign : Symbol(Foreign, Decl(thisIndependentInterfaceMembers.ts, 29, 29)) +>self : Symbol(Foreign.self, Decl(thisIndependentInterfaceMembers.ts, 30, 23)) +>foreign : Symbol(Foreign.foreign, Decl(thisIndependentInterfaceMembers.ts, 30, 37)) + + type Copy = { readonly [K in keyof T]: T[K] }; +>Copy : Symbol(Copy, Decl(thisIndependentInterfaceMembers.ts, 30, 67)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 31, 14)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 31, 31)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 31, 14)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 31, 14)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 31, 31)) + + interface Copied extends Copy {} +>Copied : Symbol(Copied, Decl(thisIndependentInterfaceMembers.ts, 31, 53)) +>Copy : Symbol(Copy, Decl(thisIndependentInterfaceMembers.ts, 30, 67)) +>Foreign : Symbol(Foreign, Decl(thisIndependentInterfaceMembers.ts, 29, 29)) + + interface Derived extends Copied { readonly derived: "derived" } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 32, 45)) +>Copied : Symbol(Copied, Decl(thisIndependentInterfaceMembers.ts, 31, 53)) +>derived : Symbol(Derived.derived, Decl(thisIndependentInterfaceMembers.ts, 33, 38)) + + declare const value: Derived; +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 34, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 32, 45)) + + const exact: Foreign = value.self(); +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 35, 9)) +>Foreign : Symbol(Foreign, Decl(thisIndependentInterfaceMembers.ts, 29, 29)) +>value.self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 30, 23)) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 34, 17)) +>self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 30, 23)) + + // @ts-expect-error A concrete map does not rebind the foreign method's this. + const wrong: Derived = value.self(); +>wrong : Symbol(wrong, Decl(thisIndependentInterfaceMembers.ts, 37, 9)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 32, 45)) +>value.self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 30, 23)) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 34, 17)) +>self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 30, 23)) +} + +namespace mergedDeclarations { +>mergedDeclarations : Symbol(mergedDeclarations, Decl(thisIndependentInterfaceMembers.ts, 38, 1)) + + type Marker = { readonly [K in "marker"]: "marker" }; +>Marker : Symbol(Marker, Decl(thisIndependentInterfaceMembers.ts, 40, 30)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 41, 30)) + + interface Merged extends Marker {} +>Merged : Symbol(Merged, Decl(thisIndependentInterfaceMembers.ts, 41, 57), Decl(thisIndependentInterfaceMembers.ts, 42, 38)) +>Marker : Symbol(Marker, Decl(thisIndependentInterfaceMembers.ts, 40, 30)) + + interface Merged { self(): this } +>Merged : Symbol(Merged, Decl(thisIndependentInterfaceMembers.ts, 41, 57), Decl(thisIndependentInterfaceMembers.ts, 42, 38)) +>self : Symbol(Merged.self, Decl(thisIndependentInterfaceMembers.ts, 43, 22)) + + interface Derived extends Merged { readonly derived: "derived" } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 43, 37)) +>Merged : Symbol(Merged, Decl(thisIndependentInterfaceMembers.ts, 41, 57), Decl(thisIndependentInterfaceMembers.ts, 42, 38)) +>derived : Symbol(Derived.derived, Decl(thisIndependentInterfaceMembers.ts, 44, 38)) + + declare const value: Derived; +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 45, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 43, 37)) + + const exact: Derived = value.self(); +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 46, 9)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 43, 37)) +>value.self : Symbol(Merged.self, Decl(thisIndependentInterfaceMembers.ts, 43, 22)) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 45, 17)) +>self : Symbol(Merged.self, Decl(thisIndependentInterfaceMembers.ts, 43, 22)) + + const marker: "marker" = value.marker; +>marker : Symbol(marker, Decl(thisIndependentInterfaceMembers.ts, 47, 9)) +>value.marker : Symbol(marker) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 45, 17)) +>marker : Symbol(marker) + + interface Plain extends Marker {} +>Plain : Symbol(Plain, Decl(thisIndependentInterfaceMembers.ts, 47, 42), Decl(thisIndependentInterfaceMembers.ts, 49, 37)) +>Marker : Symbol(Marker, Decl(thisIndependentInterfaceMembers.ts, 40, 30)) + + interface Plain { readonly merged: "merged" } +>Plain : Symbol(Plain, Decl(thisIndependentInterfaceMembers.ts, 47, 42), Decl(thisIndependentInterfaceMembers.ts, 49, 37)) +>merged : Symbol(Plain.merged, Decl(thisIndependentInterfaceMembers.ts, 50, 21)) + + interface PlainDerived extends Plain { readonly derived: "derived" } +>PlainDerived : Symbol(PlainDerived, Decl(thisIndependentInterfaceMembers.ts, 50, 49)) +>Plain : Symbol(Plain, Decl(thisIndependentInterfaceMembers.ts, 47, 42), Decl(thisIndependentInterfaceMembers.ts, 49, 37)) +>derived : Symbol(PlainDerived.derived, Decl(thisIndependentInterfaceMembers.ts, 51, 42)) + + declare const plain: PlainDerived; +>plain : Symbol(plain, Decl(thisIndependentInterfaceMembers.ts, 52, 17)) +>PlainDerived : Symbol(PlainDerived, Decl(thisIndependentInterfaceMembers.ts, 50, 49)) + + const inherited: "merged" = plain.merged; +>inherited : Symbol(inherited, Decl(thisIndependentInterfaceMembers.ts, 53, 9)) +>plain.merged : Symbol(Plain.merged, Decl(thisIndependentInterfaceMembers.ts, 50, 21)) +>plain : Symbol(plain, Decl(thisIndependentInterfaceMembers.ts, 52, 17)) +>merged : Symbol(Plain.merged, Decl(thisIndependentInterfaceMembers.ts, 50, 21)) +} + +namespace genericInterfaces { +>genericInterfaces : Symbol(genericInterfaces, Decl(thisIndependentInterfaceMembers.ts, 54, 1)) + + type Marker = { readonly [K in "value"]: T }; +>Marker : Symbol(Marker, Decl(thisIndependentInterfaceMembers.ts, 56, 29)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 57, 16)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 57, 33)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 57, 16)) + + interface Generic extends Marker { self(): this } +>Generic : Symbol(Generic, Decl(thisIndependentInterfaceMembers.ts, 57, 52)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 58, 22)) +>Marker : Symbol(Marker, Decl(thisIndependentInterfaceMembers.ts, 56, 29)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 58, 22)) +>self : Symbol(Generic.self, Decl(thisIndependentInterfaceMembers.ts, 58, 44)) + + interface NumberValue extends Generic { readonly numeric: "numeric" } +>NumberValue : Symbol(NumberValue, Decl(thisIndependentInterfaceMembers.ts, 58, 59)) +>Generic : Symbol(Generic, Decl(thisIndependentInterfaceMembers.ts, 57, 52)) +>numeric : Symbol(NumberValue.numeric, Decl(thisIndependentInterfaceMembers.ts, 59, 51)) + + interface StringValue extends Generic { readonly textual: "textual" } +>StringValue : Symbol(StringValue, Decl(thisIndependentInterfaceMembers.ts, 59, 81)) +>Generic : Symbol(Generic, Decl(thisIndependentInterfaceMembers.ts, 57, 52)) +>textual : Symbol(StringValue.textual, Decl(thisIndependentInterfaceMembers.ts, 60, 51)) + + declare const numeric: NumberValue; +>numeric : Symbol(numeric, Decl(thisIndependentInterfaceMembers.ts, 61, 17)) +>NumberValue : Symbol(NumberValue, Decl(thisIndependentInterfaceMembers.ts, 58, 59)) + + declare const textual: StringValue; +>textual : Symbol(textual, Decl(thisIndependentInterfaceMembers.ts, 62, 17)) +>StringValue : Symbol(StringValue, Decl(thisIndependentInterfaceMembers.ts, 59, 81)) + + const numberValue: number = numeric.value; +>numberValue : Symbol(numberValue, Decl(thisIndependentInterfaceMembers.ts, 63, 9)) +>numeric.value : Symbol(value) +>numeric : Symbol(numeric, Decl(thisIndependentInterfaceMembers.ts, 61, 17)) +>value : Symbol(value) + + const stringValue: string = textual.value; +>stringValue : Symbol(stringValue, Decl(thisIndependentInterfaceMembers.ts, 64, 9)) +>textual.value : Symbol(value) +>textual : Symbol(textual, Decl(thisIndependentInterfaceMembers.ts, 62, 17)) +>value : Symbol(value) + + const exact: NumberValue = numeric.self(); +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 65, 9)) +>NumberValue : Symbol(NumberValue, Decl(thisIndependentInterfaceMembers.ts, 58, 59)) +>numeric.self : Symbol(Generic.self, Decl(thisIndependentInterfaceMembers.ts, 58, 44)) +>numeric : Symbol(numeric, Decl(thisIndependentInterfaceMembers.ts, 61, 17)) +>self : Symbol(Generic.self, Decl(thisIndependentInterfaceMembers.ts, 58, 44)) + + // @ts-expect-error Distinct type arguments may not share member substitutions. + const wrong: number = textual.value; +>wrong : Symbol(wrong, Decl(thisIndependentInterfaceMembers.ts, 67, 9)) +>textual.value : Symbol(value) +>textual : Symbol(textual, Decl(thisIndependentInterfaceMembers.ts, 62, 17)) +>value : Symbol(value) + + function enclosing(input: T) { +>enclosing : Symbol(enclosing, Decl(thisIndependentInterfaceMembers.ts, 67, 40)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 69, 23)) +>input : Symbol(input, Decl(thisIndependentInterfaceMembers.ts, 69, 26)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 69, 23)) + + type OuterMap = { readonly [K in "value"]: T }; +>OuterMap : Symbol(OuterMap, Decl(thisIndependentInterfaceMembers.ts, 69, 37)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 70, 36)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 69, 23)) + + interface Local extends OuterMap {} +>Local : Symbol(Local, Decl(thisIndependentInterfaceMembers.ts, 70, 55)) +>OuterMap : Symbol(OuterMap, Decl(thisIndependentInterfaceMembers.ts, 69, 37)) + + interface Derived extends Local { readonly local: "local" } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 71, 43)) +>Local : Symbol(Local, Decl(thisIndependentInterfaceMembers.ts, 70, 55)) +>local : Symbol(Derived.local, Decl(thisIndependentInterfaceMembers.ts, 72, 41)) + + const value = null! as Derived; +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 73, 13)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 71, 43)) + + const exact: T = value.value; +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 74, 13)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 69, 23)) +>value.value : Symbol(value) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 73, 13)) +>value : Symbol(value) + + return { input, exact }; +>input : Symbol(input, Decl(thisIndependentInterfaceMembers.ts, 75, 16)) +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 75, 23)) + } + const numericOuter: number = enclosing(1).exact; +>numericOuter : Symbol(numericOuter, Decl(thisIndependentInterfaceMembers.ts, 77, 9)) +>enclosing(1).exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 75, 23)) +>enclosing : Symbol(enclosing, Decl(thisIndependentInterfaceMembers.ts, 67, 40)) +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 75, 23)) + + const stringOuter: string = enclosing("text").exact; +>stringOuter : Symbol(stringOuter, Decl(thisIndependentInterfaceMembers.ts, 78, 9)) +>enclosing("text").exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 75, 23)) +>enclosing : Symbol(enclosing, Decl(thisIndependentInterfaceMembers.ts, 67, 40)) +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 75, 23)) + + // @ts-expect-error An enclosing generic remains specific to the invocation. + const wrongOuter: number = enclosing("text").exact; +>wrongOuter : Symbol(wrongOuter, Decl(thisIndependentInterfaceMembers.ts, 80, 9)) +>enclosing("text").exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 75, 23)) +>enclosing : Symbol(enclosing, Decl(thisIndependentInterfaceMembers.ts, 67, 40)) +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 75, 23)) +} + +namespace mappedAliasArguments { +>mappedAliasArguments : Symbol(mappedAliasArguments, Decl(thisIndependentInterfaceMembers.ts, 81, 1)) + + type Value = { readonly [K in "value"]: T }; +>Value : Symbol(Value, Decl(thisIndependentInterfaceMembers.ts, 83, 32)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 84, 15)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 84, 32)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 84, 15)) + + type Alias = Value; +>Alias : Symbol(Alias, Decl(thisIndependentInterfaceMembers.ts, 84, 51)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 85, 15)) +>Value : Symbol(Value, Decl(thisIndependentInterfaceMembers.ts, 83, 32)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 85, 15)) + + interface Generic extends Alias {} +>Generic : Symbol(Generic, Decl(thisIndependentInterfaceMembers.ts, 85, 29)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 86, 22)) +>Alias : Symbol(Alias, Decl(thisIndependentInterfaceMembers.ts, 84, 51)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 86, 22)) + + interface NumberValue extends Generic {} +>NumberValue : Symbol(NumberValue, Decl(thisIndependentInterfaceMembers.ts, 86, 44)) +>Generic : Symbol(Generic, Decl(thisIndependentInterfaceMembers.ts, 85, 29)) + + interface StringValue extends Generic {} +>StringValue : Symbol(StringValue, Decl(thisIndependentInterfaceMembers.ts, 87, 52)) +>Generic : Symbol(Generic, Decl(thisIndependentInterfaceMembers.ts, 85, 29)) + + declare const numeric: NumberValue; +>numeric : Symbol(numeric, Decl(thisIndependentInterfaceMembers.ts, 89, 17)) +>NumberValue : Symbol(NumberValue, Decl(thisIndependentInterfaceMembers.ts, 86, 44)) + + declare const textual: StringValue; +>textual : Symbol(textual, Decl(thisIndependentInterfaceMembers.ts, 90, 17)) +>StringValue : Symbol(StringValue, Decl(thisIndependentInterfaceMembers.ts, 87, 52)) + + const numberValue: number = numeric.value; +>numberValue : Symbol(numberValue, Decl(thisIndependentInterfaceMembers.ts, 91, 9)) +>numeric.value : Symbol(value) +>numeric : Symbol(numeric, Decl(thisIndependentInterfaceMembers.ts, 89, 17)) +>value : Symbol(value) + + const stringValue: string = textual.value; +>stringValue : Symbol(stringValue, Decl(thisIndependentInterfaceMembers.ts, 92, 9)) +>textual.value : Symbol(value) +>textual : Symbol(textual, Decl(thisIndependentInterfaceMembers.ts, 90, 17)) +>value : Symbol(value) + + // @ts-expect-error A mapped alias must retain its variable-containing argument. + const wrong: number = textual.value; +>wrong : Symbol(wrong, Decl(thisIndependentInterfaceMembers.ts, 94, 9)) +>textual.value : Symbol(value) +>textual : Symbol(textual, Decl(thisIndependentInterfaceMembers.ts, 90, 17)) +>value : Symbol(value) + + interface Foreign { self(): this; readonly foreign: "foreign" } +>Foreign : Symbol(Foreign, Decl(thisIndependentInterfaceMembers.ts, 94, 40)) +>self : Symbol(Foreign.self, Decl(thisIndependentInterfaceMembers.ts, 96, 23)) +>foreign : Symbol(Foreign.foreign, Decl(thisIndependentInterfaceMembers.ts, 96, 37)) + + type Copy = { readonly [K in keyof T]: T[K] }; +>Copy : Symbol(Copy, Decl(thisIndependentInterfaceMembers.ts, 96, 67)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 97, 14)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 97, 31)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 97, 14)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 97, 14)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 97, 31)) + + type ConcreteAlias = Copy; +>ConcreteAlias : Symbol(ConcreteAlias, Decl(thisIndependentInterfaceMembers.ts, 97, 53)) +>Copy : Symbol(Copy, Decl(thisIndependentInterfaceMembers.ts, 96, 67)) +>Foreign : Symbol(Foreign, Decl(thisIndependentInterfaceMembers.ts, 94, 40)) + + interface Copied extends ConcreteAlias {} +>Copied : Symbol(Copied, Decl(thisIndependentInterfaceMembers.ts, 98, 39)) +>ConcreteAlias : Symbol(ConcreteAlias, Decl(thisIndependentInterfaceMembers.ts, 97, 53)) + + interface Derived extends Copied { readonly derived: "derived" } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 99, 45)) +>Copied : Symbol(Copied, Decl(thisIndependentInterfaceMembers.ts, 98, 39)) +>derived : Symbol(Derived.derived, Decl(thisIndependentInterfaceMembers.ts, 100, 38)) + + declare const derived: Derived; +>derived : Symbol(derived, Decl(thisIndependentInterfaceMembers.ts, 101, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 99, 45)) + + const foreign: Foreign = derived.self(); +>foreign : Symbol(foreign, Decl(thisIndependentInterfaceMembers.ts, 102, 9)) +>Foreign : Symbol(Foreign, Decl(thisIndependentInterfaceMembers.ts, 94, 40)) +>derived.self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 96, 23)) +>derived : Symbol(derived, Decl(thisIndependentInterfaceMembers.ts, 101, 17)) +>self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 96, 23)) + + // @ts-expect-error Mapping must preserve foreign-this even behind an alias. + const notDerived: Derived = derived.self(); +>notDerived : Symbol(notDerived, Decl(thisIndependentInterfaceMembers.ts, 104, 9)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 99, 45)) +>derived.self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 96, 23)) +>derived : Symbol(derived, Decl(thisIndependentInterfaceMembers.ts, 101, 17)) +>self : Symbol(self, Decl(thisIndependentInterfaceMembers.ts, 96, 23)) +} + +namespace recursiveMembers { +>recursiveMembers : Symbol(recursiveMembers, Decl(thisIndependentInterfaceMembers.ts, 105, 1)) + + type Links = { readonly [K in "next"]: Node }; +>Links : Symbol(Links, Decl(thisIndependentInterfaceMembers.ts, 107, 28)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 108, 29)) +>Node : Symbol(Node, Decl(thisIndependentInterfaceMembers.ts, 108, 50)) + + interface Node extends Links { readonly value: number } +>Node : Symbol(Node, Decl(thisIndependentInterfaceMembers.ts, 108, 50)) +>Links : Symbol(Links, Decl(thisIndependentInterfaceMembers.ts, 107, 28)) +>value : Symbol(Node.value, Decl(thisIndependentInterfaceMembers.ts, 109, 34)) + + interface Derived extends Node { readonly own: string } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 109, 59)) +>Node : Symbol(Node, Decl(thisIndependentInterfaceMembers.ts, 108, 50)) +>own : Symbol(Derived.own, Decl(thisIndependentInterfaceMembers.ts, 110, 36)) + + declare const value: Derived; +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 111, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 109, 59)) + + const next: Node = value.next; +>next : Symbol(next, Decl(thisIndependentInterfaceMembers.ts, 112, 9)) +>Node : Symbol(Node, Decl(thisIndependentInterfaceMembers.ts, 108, 50)) +>value.next : Symbol(next) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 111, 17)) +>next : Symbol(next) + + const nested: number = value.next.next.value; +>nested : Symbol(nested, Decl(thisIndependentInterfaceMembers.ts, 113, 9)) +>value.next.next.value : Symbol(Node.value, Decl(thisIndependentInterfaceMembers.ts, 109, 34)) +>value.next.next : Symbol(next) +>value.next : Symbol(next) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 111, 17)) +>next : Symbol(next) +>next : Symbol(next) +>value : Symbol(Node.value, Decl(thisIndependentInterfaceMembers.ts, 109, 34)) + + // @ts-expect-error Recursive members retain the declared Node, not Derived. + const wrong: Derived = value.next; +>wrong : Symbol(wrong, Decl(thisIndependentInterfaceMembers.ts, 115, 9)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 109, 59)) +>value.next : Symbol(next) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 111, 17)) +>next : Symbol(next) + + // This forces lookup of a mapped member through a recursively referenced type. + type Reenter = { readonly [K in "item"]: T }; +>Reenter : Symbol(Reenter, Decl(thisIndependentInterfaceMembers.ts, 115, 38)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 118, 17)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 118, 34)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 118, 17)) + + interface Recursive extends Reenter { readonly own: "own" } +>Recursive : Symbol(Recursive, Decl(thisIndependentInterfaceMembers.ts, 118, 52)) +>Reenter : Symbol(Reenter, Decl(thisIndependentInterfaceMembers.ts, 115, 38)) +>Recursive : Symbol(Recursive, Decl(thisIndependentInterfaceMembers.ts, 118, 52)) +>own : Symbol(Recursive.own, Decl(thisIndependentInterfaceMembers.ts, 119, 52)) + + interface RecursiveDerived extends Recursive { readonly more: "more" } +>RecursiveDerived : Symbol(RecursiveDerived, Decl(thisIndependentInterfaceMembers.ts, 119, 74)) +>Recursive : Symbol(Recursive, Decl(thisIndependentInterfaceMembers.ts, 118, 52)) +>more : Symbol(RecursiveDerived.more, Decl(thisIndependentInterfaceMembers.ts, 120, 50)) + + declare const recursive: RecursiveDerived; +>recursive : Symbol(recursive, Decl(thisIndependentInterfaceMembers.ts, 121, 17)) +>RecursiveDerived : Symbol(RecursiveDerived, Decl(thisIndependentInterfaceMembers.ts, 119, 74)) + + const own: "own" = recursive.item.item.own; +>own : Symbol(own, Decl(thisIndependentInterfaceMembers.ts, 122, 9)) +>recursive.item.item.own : Symbol(Recursive.own, Decl(thisIndependentInterfaceMembers.ts, 119, 52)) +>recursive.item.item : Symbol(item) +>recursive.item : Symbol(item) +>recursive : Symbol(recursive, Decl(thisIndependentInterfaceMembers.ts, 121, 17)) +>item : Symbol(item) +>item : Symbol(item) +>own : Symbol(Recursive.own, Decl(thisIndependentInterfaceMembers.ts, 119, 52)) + + // @ts-expect-error A re-entrant lookup does not substitute a descendant. + const wrongRecursive: RecursiveDerived = recursive.item; +>wrongRecursive : Symbol(wrongRecursive, Decl(thisIndependentInterfaceMembers.ts, 124, 9)) +>RecursiveDerived : Symbol(RecursiveDerived, Decl(thisIndependentInterfaceMembers.ts, 119, 74)) +>recursive.item : Symbol(item) +>recursive : Symbol(recursive, Decl(thisIndependentInterfaceMembers.ts, 121, 17)) +>item : Symbol(item) +} + +namespace rejectedBases { +>rejectedBases : Symbol(rejectedBases, Decl(thisIndependentInterfaceMembers.ts, 125, 1)) + + type LeftMap = { readonly [K in "value"]: number }; +>LeftMap : Symbol(LeftMap, Decl(thisIndependentInterfaceMembers.ts, 127, 25)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 128, 31)) + + type RightMap = { readonly [K in "value"]: string }; +>RightMap : Symbol(RightMap, Decl(thisIndependentInterfaceMembers.ts, 128, 55)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 129, 32)) + + interface Left extends LeftMap {} +>Left : Symbol(Left, Decl(thisIndependentInterfaceMembers.ts, 129, 56)) +>LeftMap : Symbol(LeftMap, Decl(thisIndependentInterfaceMembers.ts, 127, 25)) + + interface Right extends RightMap {} +>Right : Symbol(Right, Decl(thisIndependentInterfaceMembers.ts, 130, 37)) +>RightMap : Symbol(RightMap, Decl(thisIndependentInterfaceMembers.ts, 128, 55)) + + // @ts-expect-error Conflicting inherited properties must still reject. + interface Conflict extends Left, Right {} +>Conflict : Symbol(Conflict, Decl(thisIndependentInterfaceMembers.ts, 131, 39)) +>Left : Symbol(Left, Decl(thisIndependentInterfaceMembers.ts, 129, 56)) +>Right : Symbol(Right, Decl(thisIndependentInterfaceMembers.ts, 130, 37)) + + // @ts-expect-error A circular mapped constraint remains an error. + type Reentered = { [K in keyof Circular]: Circular[K] }; +>Reentered : Symbol(Reentered, Decl(thisIndependentInterfaceMembers.ts, 133, 45)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 136, 24)) +>Circular : Symbol(Circular, Decl(thisIndependentInterfaceMembers.ts, 136, 60)) +>Circular : Symbol(Circular, Decl(thisIndependentInterfaceMembers.ts, 136, 60)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 136, 24)) + + // @ts-expect-error Resolution placeholders must not prove valid ancestry. + interface Circular extends Reentered { readonly own: string } +>Circular : Symbol(Circular, Decl(thisIndependentInterfaceMembers.ts, 136, 60)) +>Reentered : Symbol(Reentered, Decl(thisIndependentInterfaceMembers.ts, 133, 45)) +>own : Symbol(Circular.own, Decl(thisIndependentInterfaceMembers.ts, 138, 42)) + + // @ts-expect-error An unresolved mapped key still reports its error. + type ErrorMap = { [K in MissingKey]: number }; +>ErrorMap : Symbol(ErrorMap, Decl(thisIndependentInterfaceMembers.ts, 138, 65)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 141, 23)) +>MissingKey : Symbol(MissingKey) + + interface ErrorBase extends ErrorMap {} +>ErrorBase : Symbol(ErrorBase, Decl(thisIndependentInterfaceMembers.ts, 141, 50)) +>ErrorMap : Symbol(ErrorMap, Decl(thisIndependentInterfaceMembers.ts, 138, 65)) + + // @ts-expect-error The erroneous key must not erase the numeric index contract. + interface ErrorDerived extends ErrorBase { readonly own: "own" } +>ErrorDerived : Symbol(ErrorDerived, Decl(thisIndependentInterfaceMembers.ts, 142, 43)) +>ErrorBase : Symbol(ErrorBase, Decl(thisIndependentInterfaceMembers.ts, 141, 50)) +>own : Symbol(ErrorDerived.own, Decl(thisIndependentInterfaceMembers.ts, 144, 46)) +} + +namespace concreteMappedMembers { +>concreteMappedMembers : Symbol(concreteMappedMembers, Decl(thisIndependentInterfaceMembers.ts, 145, 1)) + + type Value = { readonly [K in "value"]: T }; +>Value : Symbol(Value, Decl(thisIndependentInterfaceMembers.ts, 147, 33)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 148, 15)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 148, 32)) +>T : Symbol(T, Decl(thisIndependentInterfaceMembers.ts, 148, 15)) + + interface Plain extends Value {} +>Plain : Symbol(Plain, Decl(thisIndependentInterfaceMembers.ts, 148, 51)) +>Value : Symbol(Value, Decl(thisIndependentInterfaceMembers.ts, 147, 33)) + + interface Derived extends Plain { readonly own: "own" } +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 149, 44)) +>Plain : Symbol(Plain, Decl(thisIndependentInterfaceMembers.ts, 148, 51)) +>own : Symbol(Derived.own, Decl(thisIndependentInterfaceMembers.ts, 150, 37)) + + declare const value: Derived; +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 151, 17)) +>Derived : Symbol(Derived, Decl(thisIndependentInterfaceMembers.ts, 149, 44)) + + const exact: number = value.value; +>exact : Symbol(exact, Decl(thisIndependentInterfaceMembers.ts, 152, 9)) +>value.value : Symbol(value) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 151, 17)) +>value : Symbol(value) + + // @ts-expect-error Concrete mapped properties keep their exact type. + const wrong: string = value.value; +>wrong : Symbol(wrong, Decl(thisIndependentInterfaceMembers.ts, 154, 9)) +>value.value : Symbol(value) +>value : Symbol(value, Decl(thisIndependentInterfaceMembers.ts, 151, 17)) +>value : Symbol(value) + + interface WithIndex { readonly [key: string]: number } +>WithIndex : Symbol(WithIndex, Decl(thisIndependentInterfaceMembers.ts, 154, 38)) +>key : Symbol(key, Decl(thisIndependentInterfaceMembers.ts, 156, 36)) + + interface IndexBase extends WithIndex {} +>IndexBase : Symbol(IndexBase, Decl(thisIndependentInterfaceMembers.ts, 156, 58)) +>WithIndex : Symbol(WithIndex, Decl(thisIndependentInterfaceMembers.ts, 154, 38)) + + interface IndexDerived extends IndexBase { readonly own: 1 } +>IndexDerived : Symbol(IndexDerived, Decl(thisIndependentInterfaceMembers.ts, 157, 44)) +>IndexBase : Symbol(IndexBase, Decl(thisIndependentInterfaceMembers.ts, 156, 58)) +>own : Symbol(IndexDerived.own, Decl(thisIndependentInterfaceMembers.ts, 158, 46)) + + declare const indexed: IndexDerived; +>indexed : Symbol(indexed, Decl(thisIndependentInterfaceMembers.ts, 159, 17)) +>IndexDerived : Symbol(IndexDerived, Decl(thisIndependentInterfaceMembers.ts, 157, 44)) + + const entry: number = indexed["arbitrary"]; +>entry : Symbol(entry, Decl(thisIndependentInterfaceMembers.ts, 160, 9)) +>indexed : Symbol(indexed, Decl(thisIndependentInterfaceMembers.ts, 159, 17)) + + type Callable = { (): number; readonly [K: string]: unknown }; +>Callable : Symbol(Callable, Decl(thisIndependentInterfaceMembers.ts, 160, 47)) +>K : Symbol(K, Decl(thisIndependentInterfaceMembers.ts, 162, 44)) + + interface CallBase extends Callable {} +>CallBase : Symbol(CallBase, Decl(thisIndependentInterfaceMembers.ts, 162, 66)) +>Callable : Symbol(Callable, Decl(thisIndependentInterfaceMembers.ts, 160, 47)) + + interface CallDerived extends CallBase { readonly own: "own" } +>CallDerived : Symbol(CallDerived, Decl(thisIndependentInterfaceMembers.ts, 163, 42)) +>CallBase : Symbol(CallBase, Decl(thisIndependentInterfaceMembers.ts, 162, 66)) +>own : Symbol(CallDerived.own, Decl(thisIndependentInterfaceMembers.ts, 164, 44)) + + declare const callable: CallDerived; +>callable : Symbol(callable, Decl(thisIndependentInterfaceMembers.ts, 165, 17)) +>CallDerived : Symbol(CallDerived, Decl(thisIndependentInterfaceMembers.ts, 163, 42)) + + const result: number = callable(); +>result : Symbol(result, Decl(thisIndependentInterfaceMembers.ts, 166, 9)) +>callable : Symbol(callable, Decl(thisIndependentInterfaceMembers.ts, 165, 17)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.types b/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.types new file mode 100644 index 0000000000000..b327d45362a56 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisIndependentInterfaceMembers.types @@ -0,0 +1,493 @@ +//// [tests/cases/compiler/thisIndependentInterfaceMembers.ts] //// + +=== thisIndependentInterfaceMembers.ts === +// The member reuse optimization must preserve real, outer and foreign `this`. +namespace capturedOuterThis { +>capturedOuterThis : typeof capturedOuterThis + + class Owner { +>Owner : Owner + + read() { +>read : () => this + + type OuterMap = { readonly [K in "owner"]: this }; +>OuterMap : { readonly owner: this; } + + interface Base extends OuterMap {} + interface Derived extends Base { readonly derived: "derived" } +>derived : "derived" + + const value = null! as Derived; +>value : Derived +>null! as Derived : Derived +>null! : never + + const exact: this = value.owner; +>exact : this +>value.owner : this +>value : Derived +>owner : this + + // @ts-expect-error The owner is not the mapped property's interface. + const wrong: Derived = value.owner; +>wrong : Derived +>value.owner : this +>value : Derived +>owner : this + + return exact; +>exact : this + } + } + class SpecialOwner extends Owner { readonly special = "special" } +>SpecialOwner : SpecialOwner +>Owner : Owner +>special : "special" +>"special" : "special" + + declare const owner: SpecialOwner; +>owner : SpecialOwner + + const exactOwner: SpecialOwner = owner.read(); +>exactOwner : SpecialOwner +>owner.read() : SpecialOwner +>owner.read : () => SpecialOwner +>owner : SpecialOwner +>read : () => SpecialOwner +} + +namespace fluentAlias { +>fluentAlias : typeof fluentAlias + + interface Fluent { self(): this } +>self : () => this + + type FluentAlias = Fluent; +>FluentAlias : Fluent + + type Marker = { readonly [K in "marker"]: "marker" }; +>Marker : Marker + + interface Mixed extends Marker, FluentAlias {} + interface Derived extends Mixed { readonly derived: "derived" } +>derived : "derived" + + declare const value: Derived; +>value : Derived + + const exact: Derived = value.self(); +>exact : Derived +>value.self() : Derived +>value.self : () => Derived +>value : Derived +>self : () => Derived +} + +namespace foreignMappedThis { +>foreignMappedThis : typeof foreignMappedThis + + interface Foreign { self(): this; readonly foreign: "foreign" } +>self : () => this +>foreign : "foreign" + + type Copy = { readonly [K in keyof T]: T[K] }; +>Copy : Copy + + interface Copied extends Copy {} + interface Derived extends Copied { readonly derived: "derived" } +>derived : "derived" + + declare const value: Derived; +>value : Derived + + const exact: Foreign = value.self(); +>exact : Foreign +>value.self() : Foreign +>value.self : () => Foreign +>value : Derived +>self : () => Foreign + + // @ts-expect-error A concrete map does not rebind the foreign method's this. + const wrong: Derived = value.self(); +>wrong : Derived +>value.self() : Foreign +>value.self : () => Foreign +>value : Derived +>self : () => Foreign +} + +namespace mergedDeclarations { +>mergedDeclarations : typeof mergedDeclarations + + type Marker = { readonly [K in "marker"]: "marker" }; +>Marker : Marker + + interface Merged extends Marker {} + interface Merged { self(): this } +>self : () => this + + interface Derived extends Merged { readonly derived: "derived" } +>derived : "derived" + + declare const value: Derived; +>value : Derived + + const exact: Derived = value.self(); +>exact : Derived +>value.self() : Derived +>value.self : () => Derived +>value : Derived +>self : () => Derived + + const marker: "marker" = value.marker; +>marker : "marker" +>value.marker : "marker" +>value : Derived +>marker : "marker" + + interface Plain extends Marker {} + interface Plain { readonly merged: "merged" } +>merged : "merged" + + interface PlainDerived extends Plain { readonly derived: "derived" } +>derived : "derived" + + declare const plain: PlainDerived; +>plain : PlainDerived + + const inherited: "merged" = plain.merged; +>inherited : "merged" +>plain.merged : "merged" +>plain : PlainDerived +>merged : "merged" +} + +namespace genericInterfaces { +>genericInterfaces : typeof genericInterfaces + + type Marker = { readonly [K in "value"]: T }; +>Marker : Marker + + interface Generic extends Marker { self(): this } +>self : () => this + + interface NumberValue extends Generic { readonly numeric: "numeric" } +>numeric : "numeric" + + interface StringValue extends Generic { readonly textual: "textual" } +>textual : "textual" + + declare const numeric: NumberValue; +>numeric : NumberValue + + declare const textual: StringValue; +>textual : StringValue + + const numberValue: number = numeric.value; +>numberValue : number +>numeric.value : number +>numeric : NumberValue +>value : number + + const stringValue: string = textual.value; +>stringValue : string +>textual.value : string +>textual : StringValue +>value : string + + const exact: NumberValue = numeric.self(); +>exact : NumberValue +>numeric.self() : NumberValue +>numeric.self : () => NumberValue +>numeric : NumberValue +>self : () => NumberValue + + // @ts-expect-error Distinct type arguments may not share member substitutions. + const wrong: number = textual.value; +>wrong : number +>textual.value : string +>textual : StringValue +>value : string + + function enclosing(input: T) { +>enclosing : (input: T) => { input: T; exact: T; } +>input : T + + type OuterMap = { readonly [K in "value"]: T }; +>OuterMap : { readonly value: T; } + + interface Local extends OuterMap {} + interface Derived extends Local { readonly local: "local" } +>local : "local" + + const value = null! as Derived; +>value : Derived +>null! as Derived : Derived +>null! : never + + const exact: T = value.value; +>exact : T +>value.value : T +>value : Derived +>value : T + + return { input, exact }; +>{ input, exact } : { input: T; exact: T; } +>input : T +>exact : T + } + const numericOuter: number = enclosing(1).exact; +>numericOuter : number +>enclosing(1).exact : number +>enclosing(1) : { input: number; exact: number; } +>enclosing : (input: T) => { input: T; exact: T; } +>1 : 1 +>exact : number + + const stringOuter: string = enclosing("text").exact; +>stringOuter : string +>enclosing("text").exact : string +>enclosing("text") : { input: string; exact: string; } +>enclosing : (input: T) => { input: T; exact: T; } +>"text" : "text" +>exact : string + + // @ts-expect-error An enclosing generic remains specific to the invocation. + const wrongOuter: number = enclosing("text").exact; +>wrongOuter : number +>enclosing("text").exact : string +>enclosing("text") : { input: string; exact: string; } +>enclosing : (input: T) => { input: T; exact: T; } +>"text" : "text" +>exact : string +} + +namespace mappedAliasArguments { +>mappedAliasArguments : typeof mappedAliasArguments + + type Value = { readonly [K in "value"]: T }; +>Value : Value + + type Alias = Value; +>Alias : Alias + + interface Generic extends Alias {} + interface NumberValue extends Generic {} + interface StringValue extends Generic {} + declare const numeric: NumberValue; +>numeric : NumberValue + + declare const textual: StringValue; +>textual : StringValue + + const numberValue: number = numeric.value; +>numberValue : number +>numeric.value : number +>numeric : NumberValue +>value : number + + const stringValue: string = textual.value; +>stringValue : string +>textual.value : string +>textual : StringValue +>value : string + + // @ts-expect-error A mapped alias must retain its variable-containing argument. + const wrong: number = textual.value; +>wrong : number +>textual.value : string +>textual : StringValue +>value : string + + interface Foreign { self(): this; readonly foreign: "foreign" } +>self : () => this +>foreign : "foreign" + + type Copy = { readonly [K in keyof T]: T[K] }; +>Copy : Copy + + type ConcreteAlias = Copy; +>ConcreteAlias : Copy + + interface Copied extends ConcreteAlias {} + interface Derived extends Copied { readonly derived: "derived" } +>derived : "derived" + + declare const derived: Derived; +>derived : Derived + + const foreign: Foreign = derived.self(); +>foreign : Foreign +>derived.self() : Foreign +>derived.self : () => Foreign +>derived : Derived +>self : () => Foreign + + // @ts-expect-error Mapping must preserve foreign-this even behind an alias. + const notDerived: Derived = derived.self(); +>notDerived : Derived +>derived.self() : Foreign +>derived.self : () => Foreign +>derived : Derived +>self : () => Foreign +} + +namespace recursiveMembers { +>recursiveMembers : typeof recursiveMembers + + type Links = { readonly [K in "next"]: Node }; +>Links : Links + + interface Node extends Links { readonly value: number } +>value : number + + interface Derived extends Node { readonly own: string } +>own : string + + declare const value: Derived; +>value : Derived + + const next: Node = value.next; +>next : Node +>value.next : Node +>value : Derived +>next : Node + + const nested: number = value.next.next.value; +>nested : number +>value.next.next.value : number +>value.next.next : Node +>value.next : Node +>value : Derived +>next : Node +>next : Node +>value : number + + // @ts-expect-error Recursive members retain the declared Node, not Derived. + const wrong: Derived = value.next; +>wrong : Derived +>value.next : Node +>value : Derived +>next : Node + + // This forces lookup of a mapped member through a recursively referenced type. + type Reenter = { readonly [K in "item"]: T }; +>Reenter : Reenter + + interface Recursive extends Reenter { readonly own: "own" } +>own : "own" + + interface RecursiveDerived extends Recursive { readonly more: "more" } +>more : "more" + + declare const recursive: RecursiveDerived; +>recursive : RecursiveDerived + + const own: "own" = recursive.item.item.own; +>own : "own" +>recursive.item.item.own : "own" +>recursive.item.item : Recursive +>recursive.item : Recursive +>recursive : RecursiveDerived +>item : Recursive +>item : Recursive +>own : "own" + + // @ts-expect-error A re-entrant lookup does not substitute a descendant. + const wrongRecursive: RecursiveDerived = recursive.item; +>wrongRecursive : RecursiveDerived +>recursive.item : Recursive +>recursive : RecursiveDerived +>item : Recursive +} + +namespace rejectedBases { + type LeftMap = { readonly [K in "value"]: number }; +>LeftMap : LeftMap + + type RightMap = { readonly [K in "value"]: string }; +>RightMap : RightMap + + interface Left extends LeftMap {} + interface Right extends RightMap {} + // @ts-expect-error Conflicting inherited properties must still reject. + interface Conflict extends Left, Right {} + + // @ts-expect-error A circular mapped constraint remains an error. + type Reentered = { [K in keyof Circular]: Circular[K] }; +>Reentered : error + + // @ts-expect-error Resolution placeholders must not prove valid ancestry. + interface Circular extends Reentered { readonly own: string } +>own : string + + // @ts-expect-error An unresolved mapped key still reports its error. + type ErrorMap = { [K in MissingKey]: number }; +>ErrorMap : ErrorMap + + interface ErrorBase extends ErrorMap {} + // @ts-expect-error The erroneous key must not erase the numeric index contract. + interface ErrorDerived extends ErrorBase { readonly own: "own" } +>own : "own" +} + +namespace concreteMappedMembers { +>concreteMappedMembers : typeof concreteMappedMembers + + type Value = { readonly [K in "value"]: T }; +>Value : Value + + interface Plain extends Value {} + interface Derived extends Plain { readonly own: "own" } +>own : "own" + + declare const value: Derived; +>value : Derived + + const exact: number = value.value; +>exact : number +>value.value : number +>value : Derived +>value : number + + // @ts-expect-error Concrete mapped properties keep their exact type. + const wrong: string = value.value; +>wrong : string +>value.value : number +>value : Derived +>value : number + + interface WithIndex { readonly [key: string]: number } +>key : string + + interface IndexBase extends WithIndex {} + interface IndexDerived extends IndexBase { readonly own: 1 } +>own : 1 + + declare const indexed: IndexDerived; +>indexed : IndexDerived + + const entry: number = indexed["arbitrary"]; +>entry : number +>indexed["arbitrary"] : number +>indexed : IndexDerived +>"arbitrary" : "arbitrary" + + type Callable = { (): number; readonly [K: string]: unknown }; +>Callable : Callable +>K : string + + interface CallBase extends Callable {} + interface CallDerived extends CallBase { readonly own: "own" } +>own : "own" + + declare const callable: CallDerived; +>callable : CallDerived + + const result: number = callable(); +>result : number +>callable() : number +>callable : CallDerived +} + diff --git a/tsc/testdata/tests/cases/compiler/thisIndependentInterfaceMembers.ts b/tsc/testdata/tests/cases/compiler/thisIndependentInterfaceMembers.ts new file mode 100644 index 0000000000000..46a747515728a --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/thisIndependentInterfaceMembers.ts @@ -0,0 +1,170 @@ +// @strict: true + +// The member reuse optimization must preserve real, outer and foreign `this`. +namespace capturedOuterThis { + class Owner { + read() { + type OuterMap = { readonly [K in "owner"]: this }; + interface Base extends OuterMap {} + interface Derived extends Base { readonly derived: "derived" } + const value = null! as Derived; + const exact: this = value.owner; + // @ts-expect-error The owner is not the mapped property's interface. + const wrong: Derived = value.owner; + return exact; + } + } + class SpecialOwner extends Owner { readonly special = "special" } + declare const owner: SpecialOwner; + const exactOwner: SpecialOwner = owner.read(); +} + +namespace fluentAlias { + interface Fluent { self(): this } + type FluentAlias = Fluent; + type Marker = { readonly [K in "marker"]: "marker" }; + interface Mixed extends Marker, FluentAlias {} + interface Derived extends Mixed { readonly derived: "derived" } + declare const value: Derived; + const exact: Derived = value.self(); +} + +namespace foreignMappedThis { + interface Foreign { self(): this; readonly foreign: "foreign" } + type Copy = { readonly [K in keyof T]: T[K] }; + interface Copied extends Copy {} + interface Derived extends Copied { readonly derived: "derived" } + declare const value: Derived; + const exact: Foreign = value.self(); + // @ts-expect-error A concrete map does not rebind the foreign method's this. + const wrong: Derived = value.self(); +} + +namespace mergedDeclarations { + type Marker = { readonly [K in "marker"]: "marker" }; + interface Merged extends Marker {} + interface Merged { self(): this } + interface Derived extends Merged { readonly derived: "derived" } + declare const value: Derived; + const exact: Derived = value.self(); + const marker: "marker" = value.marker; + + interface Plain extends Marker {} + interface Plain { readonly merged: "merged" } + interface PlainDerived extends Plain { readonly derived: "derived" } + declare const plain: PlainDerived; + const inherited: "merged" = plain.merged; +} + +namespace genericInterfaces { + type Marker = { readonly [K in "value"]: T }; + interface Generic extends Marker { self(): this } + interface NumberValue extends Generic { readonly numeric: "numeric" } + interface StringValue extends Generic { readonly textual: "textual" } + declare const numeric: NumberValue; + declare const textual: StringValue; + const numberValue: number = numeric.value; + const stringValue: string = textual.value; + const exact: NumberValue = numeric.self(); + // @ts-expect-error Distinct type arguments may not share member substitutions. + const wrong: number = textual.value; + + function enclosing(input: T) { + type OuterMap = { readonly [K in "value"]: T }; + interface Local extends OuterMap {} + interface Derived extends Local { readonly local: "local" } + const value = null! as Derived; + const exact: T = value.value; + return { input, exact }; + } + const numericOuter: number = enclosing(1).exact; + const stringOuter: string = enclosing("text").exact; + // @ts-expect-error An enclosing generic remains specific to the invocation. + const wrongOuter: number = enclosing("text").exact; +} + +namespace mappedAliasArguments { + type Value = { readonly [K in "value"]: T }; + type Alias = Value; + interface Generic extends Alias {} + interface NumberValue extends Generic {} + interface StringValue extends Generic {} + declare const numeric: NumberValue; + declare const textual: StringValue; + const numberValue: number = numeric.value; + const stringValue: string = textual.value; + // @ts-expect-error A mapped alias must retain its variable-containing argument. + const wrong: number = textual.value; + + interface Foreign { self(): this; readonly foreign: "foreign" } + type Copy = { readonly [K in keyof T]: T[K] }; + type ConcreteAlias = Copy; + interface Copied extends ConcreteAlias {} + interface Derived extends Copied { readonly derived: "derived" } + declare const derived: Derived; + const foreign: Foreign = derived.self(); + // @ts-expect-error Mapping must preserve foreign-this even behind an alias. + const notDerived: Derived = derived.self(); +} + +namespace recursiveMembers { + type Links = { readonly [K in "next"]: Node }; + interface Node extends Links { readonly value: number } + interface Derived extends Node { readonly own: string } + declare const value: Derived; + const next: Node = value.next; + const nested: number = value.next.next.value; + // @ts-expect-error Recursive members retain the declared Node, not Derived. + const wrong: Derived = value.next; + + // This forces lookup of a mapped member through a recursively referenced type. + type Reenter = { readonly [K in "item"]: T }; + interface Recursive extends Reenter { readonly own: "own" } + interface RecursiveDerived extends Recursive { readonly more: "more" } + declare const recursive: RecursiveDerived; + const own: "own" = recursive.item.item.own; + // @ts-expect-error A re-entrant lookup does not substitute a descendant. + const wrongRecursive: RecursiveDerived = recursive.item; +} + +namespace rejectedBases { + type LeftMap = { readonly [K in "value"]: number }; + type RightMap = { readonly [K in "value"]: string }; + interface Left extends LeftMap {} + interface Right extends RightMap {} + // @ts-expect-error Conflicting inherited properties must still reject. + interface Conflict extends Left, Right {} + + // @ts-expect-error A circular mapped constraint remains an error. + type Reentered = { [K in keyof Circular]: Circular[K] }; + // @ts-expect-error Resolution placeholders must not prove valid ancestry. + interface Circular extends Reentered { readonly own: string } + + // @ts-expect-error An unresolved mapped key still reports its error. + type ErrorMap = { [K in MissingKey]: number }; + interface ErrorBase extends ErrorMap {} + // @ts-expect-error The erroneous key must not erase the numeric index contract. + interface ErrorDerived extends ErrorBase { readonly own: "own" } +} + +namespace concreteMappedMembers { + type Value = { readonly [K in "value"]: T }; + interface Plain extends Value {} + interface Derived extends Plain { readonly own: "own" } + declare const value: Derived; + const exact: number = value.value; + // @ts-expect-error Concrete mapped properties keep their exact type. + const wrong: string = value.value; + + interface WithIndex { readonly [key: string]: number } + interface IndexBase extends WithIndex {} + interface IndexDerived extends IndexBase { readonly own: 1 } + declare const indexed: IndexDerived; + const entry: number = indexed["arbitrary"]; + + type Callable = { (): number; readonly [K: string]: unknown }; + interface CallBase extends Callable {} + interface CallDerived extends CallBase { readonly own: "own" } + declare const callable: CallDerived; + const result: number = callable(); +}