Skip to content

Commit c35b143

Browse files
authored
Keep accessors as accessors in emitted anonymous class declarations (#60853)
1 parent 3267e42 commit c35b143

32 files changed

+1480
-45
lines changed

src/compiler/checker.ts

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ import {
467467
IntroducesNewScopeNode,
468468
isAccessExpression,
469469
isAccessor,
470+
isAccessorModifier,
470471
isAliasableExpression,
471472
isAmbientModule,
472473
isArray,
@@ -7690,27 +7691,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
76907691

76917692
if (propertySymbol.flags & SymbolFlags.Accessor) {
76927693
const writeType = getWriteTypeOfSymbol(propertySymbol);
7693-
if (propertyType !== writeType && !isErrorType(propertyType) && !isErrorType(writeType)) {
7694+
if (!isErrorType(propertyType) && !isErrorType(writeType)) {
76947695
const symbolMapper = getSymbolLinks(propertySymbol).mapper;
7695-
const getterDeclaration = getDeclarationOfKind<GetAccessorDeclaration>(propertySymbol, SyntaxKind.GetAccessor)!;
7696-
const getterSignature = getSignatureFromDeclaration(getterDeclaration);
7697-
typeElements.push(
7698-
setCommentRange(
7699-
context,
7700-
signatureToSignatureDeclarationHelper(symbolMapper ? instantiateSignature(getterSignature, symbolMapper) : getterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration,
7701-
getterDeclaration,
7702-
),
7703-
);
7704-
const setterDeclaration = getDeclarationOfKind<SetAccessorDeclaration>(propertySymbol, SyntaxKind.SetAccessor)!;
7705-
const setterSignature = getSignatureFromDeclaration(setterDeclaration);
7706-
typeElements.push(
7707-
setCommentRange(
7708-
context,
7709-
signatureToSignatureDeclarationHelper(symbolMapper ? instantiateSignature(setterSignature, symbolMapper) : setterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration,
7710-
setterDeclaration,
7711-
),
7712-
);
7713-
return;
7696+
const propDeclaration = getDeclarationOfKind<PropertyDeclaration>(propertySymbol, SyntaxKind.PropertyDeclaration);
7697+
if (propertyType !== writeType || propertySymbol.parent!.flags & SymbolFlags.Class && !propDeclaration) {
7698+
const getterDeclaration = getDeclarationOfKind<GetAccessorDeclaration>(propertySymbol, SyntaxKind.GetAccessor);
7699+
if (getterDeclaration) {
7700+
const getterSignature = getSignatureFromDeclaration(getterDeclaration);
7701+
typeElements.push(
7702+
setCommentRange(
7703+
context,
7704+
signatureToSignatureDeclarationHelper(symbolMapper ? instantiateSignature(getterSignature, symbolMapper) : getterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration,
7705+
getterDeclaration,
7706+
),
7707+
);
7708+
}
7709+
const setterDeclaration = getDeclarationOfKind<SetAccessorDeclaration>(propertySymbol, SyntaxKind.SetAccessor);
7710+
if (setterDeclaration) {
7711+
const setterSignature = getSignatureFromDeclaration(setterDeclaration);
7712+
typeElements.push(
7713+
setCommentRange(
7714+
context,
7715+
signatureToSignatureDeclarationHelper(symbolMapper ? instantiateSignature(setterSignature, symbolMapper) : setterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration,
7716+
setterDeclaration,
7717+
),
7718+
);
7719+
}
7720+
return;
7721+
}
7722+
if (propertySymbol.parent!.flags & SymbolFlags.Class && propDeclaration && find(propDeclaration.modifiers, isAccessorModifier)) {
7723+
const fakeGetterSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, propertyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
7724+
typeElements.push(
7725+
setCommentRange(
7726+
context,
7727+
signatureToSignatureDeclarationHelper(fakeGetterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration,
7728+
propDeclaration,
7729+
),
7730+
);
7731+
const setterParam = createSymbol(SymbolFlags.FunctionScopedVariable, "arg" as __String);
7732+
setterParam.links.type = writeType;
7733+
const fakeSetterSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, [setterParam], voidType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
7734+
typeElements.push(
7735+
signatureToSignatureDeclarationHelper(fakeSetterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration,
7736+
);
7737+
return;
7738+
}
77147739
}
77157740
}
77167741

@@ -12816,13 +12841,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1281612841
*/
1281712842
function getWriteTypeOfSymbol(symbol: Symbol): Type {
1281812843
const checkFlags = getCheckFlags(symbol);
12844+
if (checkFlags & CheckFlags.SyntheticProperty) {
12845+
return checkFlags & CheckFlags.DeferredType ?
12846+
getWriteTypeOfSymbolWithDeferredType(symbol) || getTypeOfSymbolWithDeferredType(symbol) :
12847+
// NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty
12848+
(symbol as TransientSymbol).links.writeType || (symbol as TransientSymbol).links.type!;
12849+
}
1281912850
if (symbol.flags & SymbolFlags.Property) {
12820-
return checkFlags & CheckFlags.SyntheticProperty ?
12821-
checkFlags & CheckFlags.DeferredType ?
12822-
getWriteTypeOfSymbolWithDeferredType(symbol) || getTypeOfSymbolWithDeferredType(symbol) :
12823-
// NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty
12824-
(symbol as TransientSymbol).links.writeType || (symbol as TransientSymbol).links.type! :
12825-
removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional));
12851+
return removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional));
1282612852
}
1282712853
if (symbol.flags & SymbolFlags.Accessor) {
1282812854
return checkFlags & CheckFlags.Instantiated ?
@@ -15439,6 +15465,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1543915465
}
1544015466

1544115467
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
15468+
let propFlags = SymbolFlags.None;
1544215469
let singleProp: Symbol | undefined;
1544315470
let propSet: Map<SymbolId, Symbol> | undefined;
1544415471
let indexTypes: Type[] | undefined;
@@ -15465,6 +15492,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1546515492
}
1546615493
if (!singleProp) {
1546715494
singleProp = prop;
15495+
propFlags = (prop.flags & SymbolFlags.Accessor) || SymbolFlags.Property;
1546815496
}
1546915497
else if (prop !== singleProp) {
1547015498
const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp);
@@ -15487,6 +15515,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1548715515
propSet.set(id, prop);
1548815516
}
1548915517
}
15518+
// classes created by mixins are represented as intersections
15519+
// and overriding a property in a derived class redefines it completely at runtime
15520+
// so a get accessor can't be merged with a set accessor in a base class,
15521+
// for that reason the accessor flags are only used when they are the same in all constituents
15522+
if (propFlags & SymbolFlags.Accessor && (prop.flags & SymbolFlags.Accessor) !== (propFlags & SymbolFlags.Accessor)) {
15523+
propFlags = (propFlags & ~SymbolFlags.Accessor) | SymbolFlags.Property;
15524+
}
1549015525
}
1549115526
if (isUnion && isReadonlySymbol(prop)) {
1549215527
checkFlags |= CheckFlags.Readonly;
@@ -15505,6 +15540,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1550515540
else if (isUnion) {
1550615541
const indexInfo = !isLateBoundName(name) && getApplicableIndexInfoForName(type, name);
1550715542
if (indexInfo) {
15543+
propFlags = (propFlags & ~SymbolFlags.Accessor) | SymbolFlags.Property;
1550815544
checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0);
1550915545
indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type);
1551015546
}
@@ -15583,7 +15619,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1558315619
propTypes.push(type);
1558415620
}
1558515621
addRange(propTypes, indexTypes);
15586-
const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags);
15622+
const result = createSymbol(propFlags | (optionalFlag ?? 0), name, syntheticFlag | checkFlags);
1558715623
result.links.containingType = containingType;
1558815624
if (!hasNonUniformValueDeclaration && firstValueDeclaration) {
1558915625
result.valueDeclaration = firstValueDeclaration;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//// [tests/cases/conformance/declarationEmit/anonymousClassAccessorsDeclarationEmit1.ts] ////
2+
3+
//// [anonymousClassAccessorsDeclarationEmit1.ts]
4+
export abstract class Base {
5+
accessor a = 1;
6+
}
7+
8+
export function middle(Super = Base) {
9+
abstract class Middle extends Super {}
10+
return Middle;
11+
}
12+
13+
class A {
14+
constructor(...args: any[]) {}
15+
}
16+
17+
export function Mixin<T extends typeof A>(Super: T) {
18+
return class B extends Super {
19+
get myName(): string {
20+
return "B";
21+
}
22+
set myName(arg: string) {}
23+
};
24+
}
25+
26+
27+
//// [anonymousClassAccessorsDeclarationEmit1.js]
28+
export class Base {
29+
accessor a = 1;
30+
}
31+
export function middle(Super = Base) {
32+
class Middle extends Super {
33+
}
34+
return Middle;
35+
}
36+
class A {
37+
constructor(...args) { }
38+
}
39+
export function Mixin(Super) {
40+
return class B extends Super {
41+
get myName() {
42+
return "B";
43+
}
44+
set myName(arg) { }
45+
};
46+
}
47+
48+
49+
//// [anonymousClassAccessorsDeclarationEmit1.d.ts]
50+
export declare abstract class Base {
51+
accessor a: number;
52+
}
53+
export declare function middle(Super?: typeof Base): abstract new () => {
54+
get a(): number;
55+
set a(arg: number);
56+
};
57+
declare class A {
58+
constructor(...args: any[]);
59+
}
60+
export declare function Mixin<T extends typeof A>(Super: T): {
61+
new (...args: any[]): {
62+
get myName(): string;
63+
set myName(arg: string);
64+
};
65+
} & T;
66+
export {};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [tests/cases/conformance/declarationEmit/anonymousClassAccessorsDeclarationEmit1.ts] ////
2+
3+
=== anonymousClassAccessorsDeclarationEmit1.ts ===
4+
export abstract class Base {
5+
>Base : Symbol(Base, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 0, 0))
6+
7+
accessor a = 1;
8+
>a : Symbol(Base.a, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 0, 28))
9+
}
10+
11+
export function middle(Super = Base) {
12+
>middle : Symbol(middle, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 2, 1))
13+
>Super : Symbol(Super, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 4, 23))
14+
>Base : Symbol(Base, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 0, 0))
15+
16+
abstract class Middle extends Super {}
17+
>Middle : Symbol(Middle, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 4, 38))
18+
>Super : Symbol(Super, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 4, 23))
19+
20+
return Middle;
21+
>Middle : Symbol(Middle, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 4, 38))
22+
}
23+
24+
class A {
25+
>A : Symbol(A, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 7, 1))
26+
27+
constructor(...args: any[]) {}
28+
>args : Symbol(args, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 10, 14))
29+
}
30+
31+
export function Mixin<T extends typeof A>(Super: T) {
32+
>Mixin : Symbol(Mixin, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 11, 1))
33+
>T : Symbol(T, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 13, 22))
34+
>A : Symbol(A, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 7, 1))
35+
>Super : Symbol(Super, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 13, 42))
36+
>T : Symbol(T, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 13, 22))
37+
38+
return class B extends Super {
39+
>B : Symbol(B, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 14, 8))
40+
>Super : Symbol(Super, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 13, 42))
41+
42+
get myName(): string {
43+
>myName : Symbol(B.myName, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 14, 32), Decl(anonymousClassAccessorsDeclarationEmit1.ts, 17, 5))
44+
45+
return "B";
46+
}
47+
set myName(arg: string) {}
48+
>myName : Symbol(B.myName, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 14, 32), Decl(anonymousClassAccessorsDeclarationEmit1.ts, 17, 5))
49+
>arg : Symbol(arg, Decl(anonymousClassAccessorsDeclarationEmit1.ts, 18, 15))
50+
51+
};
52+
}
53+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//// [tests/cases/conformance/declarationEmit/anonymousClassAccessorsDeclarationEmit1.ts] ////
2+
3+
=== anonymousClassAccessorsDeclarationEmit1.ts ===
4+
export abstract class Base {
5+
>Base : Base
6+
> : ^^^^
7+
8+
accessor a = 1;
9+
>a : number
10+
> : ^^^^^^
11+
>1 : 1
12+
> : ^
13+
}
14+
15+
export function middle(Super = Base) {
16+
>middle : (Super?: typeof Base) => typeof Middle
17+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
>Super : typeof Base
19+
> : ^^^^^^^^^^^
20+
>Base : typeof Base
21+
> : ^^^^^^^^^^^
22+
23+
abstract class Middle extends Super {}
24+
>Middle : Middle
25+
> : ^^^^^^
26+
>Super : Base
27+
> : ^^^^
28+
29+
return Middle;
30+
>Middle : typeof Middle
31+
> : ^^^^^^^^^^^^^
32+
}
33+
34+
class A {
35+
>A : A
36+
> : ^
37+
38+
constructor(...args: any[]) {}
39+
>args : any[]
40+
> : ^^^^^
41+
}
42+
43+
export function Mixin<T extends typeof A>(Super: T) {
44+
>Mixin : <T extends typeof A>(Super: T) => { new (...args: any[]): B; prototype: Mixin<any>.B; } & T
45+
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46+
>A : typeof A
47+
> : ^^^^^^^^
48+
>Super : T
49+
> : ^
50+
51+
return class B extends Super {
52+
>class B extends Super { get myName(): string { return "B"; } set myName(arg: string) {} } : { new (...args: any[]): B; prototype: Mixin<any>.B; } & T
53+
> : ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54+
>B : { new (...args: any[]): B; prototype: Mixin<any>.B; } & T
55+
> : ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56+
>Super : A
57+
> : ^
58+
59+
get myName(): string {
60+
>myName : string
61+
> : ^^^^^^
62+
63+
return "B";
64+
>"B" : "B"
65+
> : ^^^
66+
}
67+
set myName(arg: string) {}
68+
>myName : string
69+
> : ^^^^^^
70+
>arg : string
71+
> : ^^^^^^
72+
73+
};
74+
}
75+

tests/baselines/reference/autoAccessor8.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ declare class C2 {
4545
}
4646
declare function f(): {
4747
new (): {
48-
a: any;
48+
get a(): any;
49+
set a(arg: any);
4950
};
50-
b: any;
51+
get b(): any;
52+
set b(arg: any);
5153
};

0 commit comments

Comments
 (0)