From 6df411976410731e9419a8d9ec6286f047edeb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 12 Dec 2023 15:09:40 +0100 Subject: [PATCH 1/4] Fixed an issue with intersected abstract properties not requiring to be implemented --- src/compiler/checker.ts | 26 +++++- src/compiler/types.ts | 19 ++--- src/compiler/utilities.ts | 8 +- .../abstractIntersectedClasses1.errors.txt | 56 +++++++++++++ .../abstractIntersectedClasses1.symbols | 81 +++++++++++++++++++ .../abstractIntersectedClasses1.types | 81 +++++++++++++++++++ .../compiler/abstractIntersectedClasses1.ts | 42 ++++++++++ 7 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 tests/baselines/reference/abstractIntersectedClasses1.errors.txt create mode 100644 tests/baselines/reference/abstractIntersectedClasses1.symbols create mode 100644 tests/baselines/reference/abstractIntersectedClasses1.types create mode 100644 tests/cases/compiler/abstractIntersectedClasses1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b0ea7ea9ffb04..db5ae1b67e908 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1946,6 +1946,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var subtypeReductionCache = new Map(); var decoratorContextOverrideTypeCache = new Map(); var cachedTypes = new Map(); + var cachedUnionOrIntersectionPropertySymbols = new Map(); var evolvingArrayTypes: EvolvingArrayType[] = []; var undefinedProperties: SymbolTable = new Map(); var markerTypes = new Set(); @@ -14548,7 +14549,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkFlags |= (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | - (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); + (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0) | + (modifiers & ModifierFlags.Abstract ? CheckFlags.ContainsAbstract : 0); if (!isPrototypeProperty(prop)) { syntheticFlag = CheckFlags.SyntheticProperty; } @@ -14597,6 +14599,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; + const id = `${checkFlags & CheckFlags.ReadPartial ? 'p' : ''}${skipObjectFunctionPropertyAugment ? 's' : ''}${isUnion ? 'u' : 'i'}${getSymbolListId(props)}`; + const cached = cachedUnionOrIntersectionPropertySymbols.get(id); + if (cached) { + return cached; + } let declarations: Declaration[] | undefined; let firstType: Type | undefined; let nameType: Type | undefined; @@ -14659,6 +14666,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result.links.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); } } + cachedUnionOrIntersectionPropertySymbols.set(id, result); return result; } @@ -15733,6 +15741,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } + function getSymbolListId(symbols: readonly Symbol[] | undefined) { + let result = ""; + if (symbols) { + const length = symbols.length; + let i = 0; + while (i < length) { + if (result.length) { + result += ","; + } + result += getSymbolId(symbols[i]); + i++; + } + } + return result; + } + function getAliasId(aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { return aliasSymbol ? `@${getSymbolId(aliasSymbol)}` + (aliasTypeArguments ? `:${getTypeListId(aliasTypeArguments)}` : "") : ""; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index bc833556d8062..81e5252d19f36 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5903,15 +5903,16 @@ export const enum CheckFlags { ContainsProtected = 1 << 9, // Synthetic property with protected constituent(s) ContainsPrivate = 1 << 10, // Synthetic property with private constituent(s) ContainsStatic = 1 << 11, // Synthetic property with static constituent(s) - Late = 1 << 12, // Late-bound symbol for a computed property with a dynamic name - ReverseMapped = 1 << 13, // Property of reverse-inferred homomorphic mapped type - OptionalParameter = 1 << 14, // Optional parameter - RestParameter = 1 << 15, // Rest parameter - DeferredType = 1 << 16, // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType` - HasNeverType = 1 << 17, // Synthetic property with at least one never type in constituents - Mapped = 1 << 18, // Property of mapped type - StripOptional = 1 << 19, // Strip optionality in mapped property - Unresolved = 1 << 20, // Unresolved type alias symbol + ContainsAbstract = 1 << 12, // Synthetic property with abstract constituent(s) + Late = 1 << 13, // Late-bound symbol for a computed property with a dynamic name + ReverseMapped = 1 << 14, // Property of reverse-inferred homomorphic mapped type + OptionalParameter = 1 << 15, // Optional parameter + RestParameter = 1 << 16, // Rest parameter + DeferredType = 1 << 17, // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType` + HasNeverType = 1 << 18, // Synthetic property with at least one never type in constituents + Mapped = 1 << 19, // Property of mapped type + StripOptional = 1 << 20, // Strip optionality in mapped property + Unresolved = 1 << 21, // Unresolved type alias symbol Synthetic = SyntheticProperty | SyntheticMethod, Discriminant = HasNonUniformType | HasLiteralType, Partial = ReadPartial | WritePartial, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 347be386b6b32..cb546a6f40411 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7719,14 +7719,14 @@ export function getDeclarationModifierFlagsFromSymbol(s: Symbol, isWrite = false const flags = getCombinedModifierFlags(declaration); return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; } - if (getCheckFlags(s) & CheckFlags.Synthetic) { - // NOTE: potentially unchecked cast to TransientSymbol - const checkFlags = (s as TransientSymbol).links.checkFlags; + const checkFlags = getCheckFlags(s); + if (checkFlags & CheckFlags.Synthetic) { const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : ModifierFlags.Protected; const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; - return accessModifier | staticModifier; + const abstractModifier = checkFlags & CheckFlags.ContainsAbstract ? ModifierFlags.Abstract : 0; + return accessModifier | staticModifier | abstractModifier; } if (s.flags & SymbolFlags.Prototype) { return ModifierFlags.Public | ModifierFlags.Static; diff --git a/tests/baselines/reference/abstractIntersectedClasses1.errors.txt b/tests/baselines/reference/abstractIntersectedClasses1.errors.txt new file mode 100644 index 0000000000000..510c13d9edb3f --- /dev/null +++ b/tests/baselines/reference/abstractIntersectedClasses1.errors.txt @@ -0,0 +1,56 @@ +abstractIntersectedClasses1.ts(17,7): error TS18052: Non-abstract class 'Foo1' does not implement all abstract members of 'A & B' +abstractIntersectedClasses1.ts(18,7): error TS18052: Non-abstract class 'Foo2' does not implement all abstract members of 'A & B' +abstractIntersectedClasses1.ts(34,7): error TS18052: Non-abstract class 'Bar1' does not implement all abstract members of 'A & A2' + + +==== abstractIntersectedClasses1.ts (3 errors) ==== + // https://github.com/microsoft/TypeScript/issues/56738 + + abstract class A { + abstract a(): number; + } + + abstract class A2 { + abstract a(): number; + } + + abstract class B { + abstract b(): number; + } + + declare const Base: abstract new () => A & B; + + class Foo1 extends Base {} // error + ~~~~ +!!! error TS18052: Non-abstract class 'Foo1' does not implement all abstract members of 'A & B' +!!! related TS2515 abstractIntersectedClasses1.ts:4:12: Non-abstract class 'Foo1' does not implement inherited abstract member 'a' from class 'A & B'. +!!! related TS2515 abstractIntersectedClasses1.ts:12:12: Non-abstract class 'Foo1' does not implement inherited abstract member 'b' from class 'A & B'. + class Foo2 extends Base { // error + ~~~~ +!!! error TS18052: Non-abstract class 'Foo2' does not implement all abstract members of 'A & B' +!!! related TS2515 abstractIntersectedClasses1.ts:12:12: Non-abstract class 'Foo2' does not implement inherited abstract member 'b' from class 'A & B'. + a() { + return 10; + } + } + class Foo3 extends Base { // ok + a() { + return 10; + } + b() { + return 42; + } + } + + declare const Base2: abstract new () => A & A2; + + class Bar1 extends Base2 {} // error + ~~~~ +!!! error TS18052: Non-abstract class 'Bar1' does not implement all abstract members of 'A & A2' +!!! related TS2515 abstractIntersectedClasses1.ts:4:12: Non-abstract class 'Bar1' does not implement inherited abstract member 'a' from class 'A & A2'. + class Bar2 extends Base2 { // ok + a() { + return 100; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/abstractIntersectedClasses1.symbols b/tests/baselines/reference/abstractIntersectedClasses1.symbols new file mode 100644 index 0000000000000..7ecfce4836458 --- /dev/null +++ b/tests/baselines/reference/abstractIntersectedClasses1.symbols @@ -0,0 +1,81 @@ +//// [tests/cases/compiler/abstractIntersectedClasses1.ts] //// + +=== abstractIntersectedClasses1.ts === +// https://github.com/microsoft/TypeScript/issues/56738 + +abstract class A { +>A : Symbol(A, Decl(abstractIntersectedClasses1.ts, 0, 0)) + + abstract a(): number; +>a : Symbol(A.a, Decl(abstractIntersectedClasses1.ts, 2, 18)) +} + +abstract class A2 { +>A2 : Symbol(A2, Decl(abstractIntersectedClasses1.ts, 4, 1)) + + abstract a(): number; +>a : Symbol(A2.a, Decl(abstractIntersectedClasses1.ts, 6, 19)) +} + +abstract class B { +>B : Symbol(B, Decl(abstractIntersectedClasses1.ts, 8, 1)) + + abstract b(): number; +>b : Symbol(B.b, Decl(abstractIntersectedClasses1.ts, 10, 18)) +} + +declare const Base: abstract new () => A & B; +>Base : Symbol(Base, Decl(abstractIntersectedClasses1.ts, 14, 13)) +>A : Symbol(A, Decl(abstractIntersectedClasses1.ts, 0, 0)) +>B : Symbol(B, Decl(abstractIntersectedClasses1.ts, 8, 1)) + +class Foo1 extends Base {} // error +>Foo1 : Symbol(Foo1, Decl(abstractIntersectedClasses1.ts, 14, 45)) +>Base : Symbol(Base, Decl(abstractIntersectedClasses1.ts, 14, 13)) + +class Foo2 extends Base { // error +>Foo2 : Symbol(Foo2, Decl(abstractIntersectedClasses1.ts, 16, 26)) +>Base : Symbol(Base, Decl(abstractIntersectedClasses1.ts, 14, 13)) + + a() { +>a : Symbol(Foo2.a, Decl(abstractIntersectedClasses1.ts, 17, 25)) + + return 10; + } +} +class Foo3 extends Base { // ok +>Foo3 : Symbol(Foo3, Decl(abstractIntersectedClasses1.ts, 21, 1)) +>Base : Symbol(Base, Decl(abstractIntersectedClasses1.ts, 14, 13)) + + a() { +>a : Symbol(Foo3.a, Decl(abstractIntersectedClasses1.ts, 22, 25)) + + return 10; + } + b() { +>b : Symbol(Foo3.b, Decl(abstractIntersectedClasses1.ts, 25, 3)) + + return 42; + } +} + +declare const Base2: abstract new () => A & A2; +>Base2 : Symbol(Base2, Decl(abstractIntersectedClasses1.ts, 31, 13)) +>A : Symbol(A, Decl(abstractIntersectedClasses1.ts, 0, 0)) +>A2 : Symbol(A2, Decl(abstractIntersectedClasses1.ts, 4, 1)) + +class Bar1 extends Base2 {} // error +>Bar1 : Symbol(Bar1, Decl(abstractIntersectedClasses1.ts, 31, 47)) +>Base2 : Symbol(Base2, Decl(abstractIntersectedClasses1.ts, 31, 13)) + +class Bar2 extends Base2 { // ok +>Bar2 : Symbol(Bar2, Decl(abstractIntersectedClasses1.ts, 33, 27)) +>Base2 : Symbol(Base2, Decl(abstractIntersectedClasses1.ts, 31, 13)) + + a() { +>a : Symbol(Bar2.a, Decl(abstractIntersectedClasses1.ts, 34, 26)) + + return 100; + } +} + diff --git a/tests/baselines/reference/abstractIntersectedClasses1.types b/tests/baselines/reference/abstractIntersectedClasses1.types new file mode 100644 index 0000000000000..ee345925eff78 --- /dev/null +++ b/tests/baselines/reference/abstractIntersectedClasses1.types @@ -0,0 +1,81 @@ +//// [tests/cases/compiler/abstractIntersectedClasses1.ts] //// + +=== abstractIntersectedClasses1.ts === +// https://github.com/microsoft/TypeScript/issues/56738 + +abstract class A { +>A : A + + abstract a(): number; +>a : () => number +} + +abstract class A2 { +>A2 : A2 + + abstract a(): number; +>a : () => number +} + +abstract class B { +>B : B + + abstract b(): number; +>b : () => number +} + +declare const Base: abstract new () => A & B; +>Base : abstract new () => A & B + +class Foo1 extends Base {} // error +>Foo1 : Foo1 +>Base : A & B + +class Foo2 extends Base { // error +>Foo2 : Foo2 +>Base : A & B + + a() { +>a : () => number + + return 10; +>10 : 10 + } +} +class Foo3 extends Base { // ok +>Foo3 : Foo3 +>Base : A & B + + a() { +>a : () => number + + return 10; +>10 : 10 + } + b() { +>b : () => number + + return 42; +>42 : 42 + } +} + +declare const Base2: abstract new () => A & A2; +>Base2 : abstract new () => A & A2 + +class Bar1 extends Base2 {} // error +>Bar1 : Bar1 +>Base2 : A & A2 + +class Bar2 extends Base2 { // ok +>Bar2 : Bar2 +>Base2 : A & A2 + + a() { +>a : () => number + + return 100; +>100 : 100 + } +} + diff --git a/tests/cases/compiler/abstractIntersectedClasses1.ts b/tests/cases/compiler/abstractIntersectedClasses1.ts new file mode 100644 index 0000000000000..b12f87945807f --- /dev/null +++ b/tests/cases/compiler/abstractIntersectedClasses1.ts @@ -0,0 +1,42 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/56738 + +abstract class A { + abstract a(): number; +} + +abstract class A2 { + abstract a(): number; +} + +abstract class B { + abstract b(): number; +} + +declare const Base: abstract new () => A & B; + +class Foo1 extends Base {} // error +class Foo2 extends Base { // error + a() { + return 10; + } +} +class Foo3 extends Base { // ok + a() { + return 10; + } + b() { + return 42; + } +} + +declare const Base2: abstract new () => A & A2; + +class Bar1 extends Base2 {} // error +class Bar2 extends Base2 { // ok + a() { + return 100; + } +} From 7d4a952d3671c63cbbc4c983da91c1dc8cc4dd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 12 Dec 2023 15:26:00 +0100 Subject: [PATCH 2/4] update baseline --- .../dependentDestructuredVariables.types | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/baselines/reference/dependentDestructuredVariables.types b/tests/baselines/reference/dependentDestructuredVariables.types index 0480bb1631c62..da894f98cc6a2 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.types +++ b/tests/baselines/reference/dependentDestructuredVariables.types @@ -1268,7 +1268,7 @@ function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]) { function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { >parameterReassigned1 : ([x, y]: [1, 2] | [3, 4]) => void >x : 1 | 3 ->y : 2 | 4 +>y : 2 | 4 | undefined if (Math.random()) { >Math.random() : number @@ -1283,7 +1283,7 @@ function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { } if (y === 2) { >y === 2 : boolean ->y : 2 | 4 +>y : 2 | 4 | undefined >2 : 2 x; // 1 | 3 @@ -1294,7 +1294,7 @@ function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { >parameterReassigned2 : ([x, y]: [1, 2] | [3, 4]) => void >x : 1 | 3 ->y : 2 | 4 +>y : 2 | 4 | undefined if (Math.random()) { >Math.random() : number @@ -1304,12 +1304,12 @@ function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { y = 2; >y = 2 : 2 ->y : 2 | 4 +>y : 2 | 4 | undefined >2 : 2 } if (y === 2) { >y === 2 : boolean ->y : 2 | 4 +>y : 2 | 4 | undefined >2 : 2 x; // 1 | 3 @@ -1322,9 +1322,9 @@ function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (x, y) => { >parameterReassignedContextualRest1 : (...args: [1, 2] | [3, 4]) => void >args : [1, 2] | [3, 4] ->(x, y) => { if (Math.random()) { y = 2; } if (y === 2) { x; // 1 | 3 }} : (x: 1 | 3, y: 2 | 4) => void +>(x, y) => { if (Math.random()) { y = 2; } if (y === 2) { x; // 1 | 3 }} : (x: 1 | 3, y: 2 | 4 | undefined) => void >x : 1 | 3 ->y : 2 | 4 +>y : 2 | 4 | undefined if (Math.random()) { >Math.random() : number @@ -1334,12 +1334,12 @@ const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = ( y = 2; >y = 2 : 2 ->y : 2 | 4 +>y : 2 | 4 | undefined >2 : 2 } if (y === 2) { >y === 2 : boolean ->y : 2 | 4 +>y : 2 | 4 | undefined >2 : 2 x; // 1 | 3 From bf008058b03270d1a0fbd87bd86db5b88d0641fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 12 Dec 2023 21:27:00 +0100 Subject: [PATCH 3/4] use links instead of caching --- src/compiler/checker.ts | 52 +++++++++---------- src/compiler/types.ts | 3 ++ .../dependentDestructuredVariables.types | 18 +++---- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a7457249895b9..43e0c4300ca2f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1946,7 +1946,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var subtypeReductionCache = new Map(); var decoratorContextOverrideTypeCache = new Map(); var cachedTypes = new Map(); - var cachedUnionOrIntersectionPropertySymbols = new Map(); var evolvingArrayTypes: EvolvingArrayType[] = []; var undefinedProperties: SymbolTable = new Map(); var markerTypes = new Set(); @@ -13034,8 +13033,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return length(target.typeParameters) === length(typeArguments) ? createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])) : type; } else if (type.flags & TypeFlags.Intersection) { - const types = sameMap((type as IntersectionType).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)); - return types !== (type as IntersectionType).types ? getIntersectionType(types) : type; + const intersectionType = type as IntersectionType; + const types = sameMap(intersectionType.types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)); + if (types === intersectionType.types) { + return type; + } + const result = getIntersectionType(types); + if (result.flags & TypeFlags.Intersection) { + (result as IntersectionType).withThisArgumentTarget = intersectionType; + } + return result; } return needApparentType ? getApparentType(type) : type; } @@ -14599,11 +14606,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; - const id = `${checkFlags & CheckFlags.ReadPartial ? 'p' : ''}${skipObjectFunctionPropertyAugment ? 's' : ''}${isUnion ? 'u' : 'i'}${getSymbolListId(props)}`; - const cached = cachedUnionOrIntersectionPropertySymbols.get(id); - if (cached) { - return cached; - } let declarations: Declaration[] | undefined; let firstType: Type | undefined; let nameType: Type | undefined; @@ -14666,7 +14668,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result.links.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); } } - cachedUnionOrIntersectionPropertySymbols.set(id, result); + if (!isUnion && (containingType as IntersectionType).withThisArgumentTarget) { + result.links.withThisArgumentIntersectionPropTarget = getUnionOrIntersectionProperty((containingType as IntersectionType).withThisArgumentTarget!, name, skipObjectFunctionPropertyAugment); + } return result; } @@ -15741,22 +15745,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function getSymbolListId(symbols: readonly Symbol[] | undefined) { - let result = ""; - if (symbols) { - const length = symbols.length; - let i = 0; - while (i < length) { - if (result.length) { - result += ","; - } - result += getSymbolId(symbols[i]); - i++; - } - } - return result; - } - function getAliasId(aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { return aliasSymbol ? `@${getSymbolId(aliasSymbol)}` + (aliasTypeArguments ? `:${getTypeListId(aliasTypeArguments)}` : "") : ""; } @@ -44673,11 +44661,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } - function getTargetSymbol(s: Symbol) { + function getTargetSymbol(s: Symbol): Symbol { + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols have `CheckFlags.Instantiated | CheckFlags.Synthetic` + const checkFlags = getCheckFlags(s); + // if symbol is instantiated its flags are not copied from the 'target' // so we'll need to get back original 'target' symbol to work with correct set of flags - // NOTE: cast to TransientSymbol should be safe because only TransientSymbols have CheckFlags.Instantiated - return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).links.target! : s; + if (checkFlags & CheckFlags.Instantiated) { + return (s as TransientSymbol).links.target!; + } + if (checkFlags & CheckFlags.Synthetic && (s as TransientSymbol).links.withThisArgumentIntersectionPropTarget) { + return (s as TransientSymbol).links.withThisArgumentIntersectionPropTarget!; + } + return s; } function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f0e5e58f009ec..036cc9878d330 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5879,6 +5879,7 @@ export interface SymbolLinks { tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label accessibleChainCache?: Map; filteredIndexSymbolCache?: Map //Symbol with applicable declarations + withThisArgumentIntersectionPropTarget?: Symbol; } /** @internal */ @@ -6495,6 +6496,8 @@ export interface IntersectionType extends UnionOrIntersectionType { resolvedApparentType: Type; /** @internal */ uniqueLiteralFilledInstantiation?: Type; // Instantiation with type parameters mapped to never type + /** @internal */ + withThisArgumentTarget?: IntersectionType; } export type StructuredType = ObjectType | UnionType | IntersectionType; diff --git a/tests/baselines/reference/dependentDestructuredVariables.types b/tests/baselines/reference/dependentDestructuredVariables.types index da894f98cc6a2..0480bb1631c62 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.types +++ b/tests/baselines/reference/dependentDestructuredVariables.types @@ -1268,7 +1268,7 @@ function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]) { function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { >parameterReassigned1 : ([x, y]: [1, 2] | [3, 4]) => void >x : 1 | 3 ->y : 2 | 4 | undefined +>y : 2 | 4 if (Math.random()) { >Math.random() : number @@ -1283,7 +1283,7 @@ function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { } if (y === 2) { >y === 2 : boolean ->y : 2 | 4 | undefined +>y : 2 | 4 >2 : 2 x; // 1 | 3 @@ -1294,7 +1294,7 @@ function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { >parameterReassigned2 : ([x, y]: [1, 2] | [3, 4]) => void >x : 1 | 3 ->y : 2 | 4 | undefined +>y : 2 | 4 if (Math.random()) { >Math.random() : number @@ -1304,12 +1304,12 @@ function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { y = 2; >y = 2 : 2 ->y : 2 | 4 | undefined +>y : 2 | 4 >2 : 2 } if (y === 2) { >y === 2 : boolean ->y : 2 | 4 | undefined +>y : 2 | 4 >2 : 2 x; // 1 | 3 @@ -1322,9 +1322,9 @@ function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (x, y) => { >parameterReassignedContextualRest1 : (...args: [1, 2] | [3, 4]) => void >args : [1, 2] | [3, 4] ->(x, y) => { if (Math.random()) { y = 2; } if (y === 2) { x; // 1 | 3 }} : (x: 1 | 3, y: 2 | 4 | undefined) => void +>(x, y) => { if (Math.random()) { y = 2; } if (y === 2) { x; // 1 | 3 }} : (x: 1 | 3, y: 2 | 4) => void >x : 1 | 3 ->y : 2 | 4 | undefined +>y : 2 | 4 if (Math.random()) { >Math.random() : number @@ -1334,12 +1334,12 @@ const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = ( y = 2; >y = 2 : 2 ->y : 2 | 4 | undefined +>y : 2 | 4 >2 : 2 } if (y === 2) { >y === 2 : boolean ->y : 2 | 4 | undefined +>y : 2 | 4 >2 : 2 x; // 1 | 3 From 715df5883059ad70f893cfa26be794783d302756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 7 Jul 2025 23:59:35 +0200 Subject: [PATCH 4/4] add extra test case --- .../abstractIntersectedClasses2.errors.txt | 37 ++++++++ .../abstractIntersectedClasses2.symbols | 63 ++++++++++++++ .../abstractIntersectedClasses2.types | 85 +++++++++++++++++++ .../compiler/abstractIntersectedClasses2.ts | 30 +++++++ 4 files changed, 215 insertions(+) create mode 100644 tests/baselines/reference/abstractIntersectedClasses2.errors.txt create mode 100644 tests/baselines/reference/abstractIntersectedClasses2.symbols create mode 100644 tests/baselines/reference/abstractIntersectedClasses2.types create mode 100644 tests/cases/compiler/abstractIntersectedClasses2.ts diff --git a/tests/baselines/reference/abstractIntersectedClasses2.errors.txt b/tests/baselines/reference/abstractIntersectedClasses2.errors.txt new file mode 100644 index 0000000000000..81597d9d16f84 --- /dev/null +++ b/tests/baselines/reference/abstractIntersectedClasses2.errors.txt @@ -0,0 +1,37 @@ +abstractIntersectedClasses2.ts(24,7): error TS2515: Non-abstract class 'ImplementationB' does not implement inherited abstract member testMethod from class 'MixinB<((abstract new (...args: any[]) => MixinA.Mixin) & { prototype: MixinA.Mixin; }) & typeof Base>.Mixin & MixinA.Mixin & Base'. +abstractIntersectedClasses2.ts(27,7): error TS2515: Non-abstract class 'ImplementationA' does not implement inherited abstract member testMethod from class 'MixinA.Mixin & Base'. + + +==== abstractIntersectedClasses2.ts (2 errors) ==== + // https://github.com/microsoft/TypeScript/issues/62014 + + type Constructor = new (...args: any[]) => {}; + + function MixinA(base: T) { + abstract class Mixin extends base { + abstract testMethod(): string; + } + + return Mixin; + } + + function MixinB(base: T) { + abstract class Mixin extends base { + abstract testMethod(): string; + } + + return Mixin; + } + + class Base {} + + // error + class ImplementationB extends MixinB(MixinA(Base)) {} + ~~~~~~~~~~~~~~~ +!!! error TS2515: Non-abstract class 'ImplementationB' does not implement inherited abstract member testMethod from class 'MixinB<((abstract new (...args: any[]) => MixinA.Mixin) & { prototype: MixinA.Mixin; }) & typeof Base>.Mixin & MixinA.Mixin & Base'. + + // error + class ImplementationA extends MixinA(Base) {} + ~~~~~~~~~~~~~~~ +!!! error TS2515: Non-abstract class 'ImplementationA' does not implement inherited abstract member testMethod from class 'MixinA.Mixin & Base'. + \ No newline at end of file diff --git a/tests/baselines/reference/abstractIntersectedClasses2.symbols b/tests/baselines/reference/abstractIntersectedClasses2.symbols new file mode 100644 index 0000000000000..f7b82f5918eb5 --- /dev/null +++ b/tests/baselines/reference/abstractIntersectedClasses2.symbols @@ -0,0 +1,63 @@ +//// [tests/cases/compiler/abstractIntersectedClasses2.ts] //// + +=== abstractIntersectedClasses2.ts === +// https://github.com/microsoft/TypeScript/issues/62014 + +type Constructor = new (...args: any[]) => {}; +>Constructor : Symbol(Constructor, Decl(abstractIntersectedClasses2.ts, 0, 0)) +>args : Symbol(args, Decl(abstractIntersectedClasses2.ts, 2, 24)) + +function MixinA(base: T) { +>MixinA : Symbol(MixinA, Decl(abstractIntersectedClasses2.ts, 2, 46)) +>T : Symbol(T, Decl(abstractIntersectedClasses2.ts, 4, 16)) +>Constructor : Symbol(Constructor, Decl(abstractIntersectedClasses2.ts, 0, 0)) +>base : Symbol(base, Decl(abstractIntersectedClasses2.ts, 4, 39)) +>T : Symbol(T, Decl(abstractIntersectedClasses2.ts, 4, 16)) + + abstract class Mixin extends base { +>Mixin : Symbol(Mixin, Decl(abstractIntersectedClasses2.ts, 4, 49)) +>base : Symbol(base, Decl(abstractIntersectedClasses2.ts, 4, 39)) + + abstract testMethod(): string; +>testMethod : Symbol(Mixin.testMethod, Decl(abstractIntersectedClasses2.ts, 5, 37)) + } + + return Mixin; +>Mixin : Symbol(Mixin, Decl(abstractIntersectedClasses2.ts, 4, 49)) +} + +function MixinB(base: T) { +>MixinB : Symbol(MixinB, Decl(abstractIntersectedClasses2.ts, 10, 1)) +>T : Symbol(T, Decl(abstractIntersectedClasses2.ts, 12, 16)) +>Constructor : Symbol(Constructor, Decl(abstractIntersectedClasses2.ts, 0, 0)) +>base : Symbol(base, Decl(abstractIntersectedClasses2.ts, 12, 39)) +>T : Symbol(T, Decl(abstractIntersectedClasses2.ts, 12, 16)) + + abstract class Mixin extends base { +>Mixin : Symbol(Mixin, Decl(abstractIntersectedClasses2.ts, 12, 49)) +>base : Symbol(base, Decl(abstractIntersectedClasses2.ts, 12, 39)) + + abstract testMethod(): string; +>testMethod : Symbol(Mixin.testMethod, Decl(abstractIntersectedClasses2.ts, 13, 37)) + } + + return Mixin; +>Mixin : Symbol(Mixin, Decl(abstractIntersectedClasses2.ts, 12, 49)) +} + +class Base {} +>Base : Symbol(Base, Decl(abstractIntersectedClasses2.ts, 18, 1)) + +// error +class ImplementationB extends MixinB(MixinA(Base)) {} +>ImplementationB : Symbol(ImplementationB, Decl(abstractIntersectedClasses2.ts, 20, 13)) +>MixinB : Symbol(MixinB, Decl(abstractIntersectedClasses2.ts, 10, 1)) +>MixinA : Symbol(MixinA, Decl(abstractIntersectedClasses2.ts, 2, 46)) +>Base : Symbol(Base, Decl(abstractIntersectedClasses2.ts, 18, 1)) + +// error +class ImplementationA extends MixinA(Base) {} +>ImplementationA : Symbol(ImplementationA, Decl(abstractIntersectedClasses2.ts, 23, 53)) +>MixinA : Symbol(MixinA, Decl(abstractIntersectedClasses2.ts, 2, 46)) +>Base : Symbol(Base, Decl(abstractIntersectedClasses2.ts, 18, 1)) + diff --git a/tests/baselines/reference/abstractIntersectedClasses2.types b/tests/baselines/reference/abstractIntersectedClasses2.types new file mode 100644 index 0000000000000..c23da7172b742 --- /dev/null +++ b/tests/baselines/reference/abstractIntersectedClasses2.types @@ -0,0 +1,85 @@ +//// [tests/cases/compiler/abstractIntersectedClasses2.ts] //// + +=== abstractIntersectedClasses2.ts === +// https://github.com/microsoft/TypeScript/issues/62014 + +type Constructor = new (...args: any[]) => {}; +>Constructor : Constructor +> : ^^^^^^^^^^^ +>args : any[] +> : ^^^^^ + +function MixinA(base: T) { +>MixinA : (base: T) => ((abstract new (...args: any[]) => Mixin) & { prototype: MixinA.Mixin; }) & T +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>base : T +> : ^ + + abstract class Mixin extends base { +>Mixin : Mixin +> : ^^^^^ +>base : {} +> : ^^ + + abstract testMethod(): string; +>testMethod : () => string +> : ^^^^^^ + } + + return Mixin; +>Mixin : ((abstract new (...args: any[]) => Mixin) & { prototype: MixinA.Mixin; }) & T +> : ^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} + +function MixinB(base: T) { +>MixinB : (base: T) => ((abstract new (...args: any[]) => Mixin) & { prototype: MixinB.Mixin; }) & T +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>base : T +> : ^ + + abstract class Mixin extends base { +>Mixin : Mixin +> : ^^^^^ +>base : {} +> : ^^ + + abstract testMethod(): string; +>testMethod : () => string +> : ^^^^^^ + } + + return Mixin; +>Mixin : ((abstract new (...args: any[]) => Mixin) & { prototype: MixinB.Mixin; }) & T +> : ^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} + +class Base {} +>Base : Base +> : ^^^^ + +// error +class ImplementationB extends MixinB(MixinA(Base)) {} +>ImplementationB : ImplementationB +> : ^^^^^^^^^^^^^^^ +>MixinB(MixinA(Base)) : MixinB<((abstract new (...args: any[]) => MixinA.Mixin) & { prototype: MixinA.Mixin; }) & typeof Base>.Mixin & MixinA.Mixin & Base +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>MixinB : (base: T) => ((abstract new (...args: any[]) => Mixin) & { prototype: MixinB.Mixin; }) & T +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>MixinA(Base) : ((abstract new (...args: any[]) => MixinA.Mixin) & { prototype: MixinA.Mixin; }) & typeof Base +> : ^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>MixinA : (base: T) => ((abstract new (...args: any[]) => Mixin) & { prototype: MixinA.Mixin; }) & T +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Base : typeof Base +> : ^^^^^^^^^^^ + +// error +class ImplementationA extends MixinA(Base) {} +>ImplementationA : ImplementationA +> : ^^^^^^^^^^^^^^^ +>MixinA(Base) : MixinA.Mixin & Base +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>MixinA : (base: T) => ((abstract new (...args: any[]) => Mixin) & { prototype: MixinA.Mixin; }) & T +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Base : typeof Base +> : ^^^^^^^^^^^ + diff --git a/tests/cases/compiler/abstractIntersectedClasses2.ts b/tests/cases/compiler/abstractIntersectedClasses2.ts new file mode 100644 index 0000000000000..6ca01f5a619ce --- /dev/null +++ b/tests/cases/compiler/abstractIntersectedClasses2.ts @@ -0,0 +1,30 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/62014 + +type Constructor = new (...args: any[]) => {}; + +function MixinA(base: T) { + abstract class Mixin extends base { + abstract testMethod(): string; + } + + return Mixin; +} + +function MixinB(base: T) { + abstract class Mixin extends base { + abstract testMethod(): string; + } + + return Mixin; +} + +class Base {} + +// error +class ImplementationB extends MixinB(MixinA(Base)) {} + +// error +class ImplementationA extends MixinA(Base) {}