Skip to content

Fixed an issue with intersected abstract properties not requiring to be implemented #56751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13965,8 +13965,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;
}
Expand Down Expand Up @@ -15532,7 +15540,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;
}
Expand Down Expand Up @@ -15645,6 +15654,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
result.links.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes);
}
}
if (!isUnion && (containingType as IntersectionType).withThisArgumentTarget) {
result.links.withThisArgumentIntersectionPropTarget = getUnionOrIntersectionProperty((containingType as IntersectionType).withThisArgumentTarget!, name, skipObjectFunctionPropertyAugment);
}
return result;
}

Expand Down Expand Up @@ -47285,11 +47297,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) {
Expand Down
22 changes: 13 additions & 9 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6107,6 +6107,7 @@ export interface SymbolLinks {
accessibleChainCache?: Map<string, Symbol[] | undefined>;
filteredIndexSymbolCache?: Map<string, Symbol> //Symbol with applicable declarations
requestedExternalEmitHelpers?: ExternalEmitHelpers; // External emit helpers already checked for this symbol.
withThisArgumentIntersectionPropTarget?: Symbol;
}

// dprint-ignore
Expand All @@ -6125,15 +6126,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,
Expand Down Expand Up @@ -6758,6 +6760,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;
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7978,14 +7978,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;
Expand Down
52 changes: 52 additions & 0 deletions tests/baselines/reference/abstractIntersectedClasses1.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
abstractIntersectedClasses1.ts(17,7): error TS2654: Non-abstract class 'Foo1' is missing implementations for the following members of 'A & B': 'a', 'b'.
abstractIntersectedClasses1.ts(18,7): error TS2515: Non-abstract class 'Foo2' does not implement inherited abstract member b from class 'A & B'.
abstractIntersectedClasses1.ts(34,7): error TS2515: Non-abstract class 'Bar1' does not implement inherited abstract member a from class '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 TS2654: Non-abstract class 'Foo1' is missing implementations for the following members of 'A & B': 'a', 'b'.
class Foo2 extends Base { // error
~~~~
!!! error TS2515: 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 TS2515: Non-abstract class 'Bar1' does not implement inherited abstract member a from class 'A & A2'.
class Bar2 extends Base2 { // ok
a() {
return 100;
}
}

81 changes: 81 additions & 0 deletions tests/baselines/reference/abstractIntersectedClasses1.symbols
Original file line number Diff line number Diff line change
@@ -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;
}
}

107 changes: 107 additions & 0 deletions tests/baselines/reference/abstractIntersectedClasses1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//// [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
> : ^^^
}
}

Loading