Skip to content

Commit 7f3c03d

Browse files
authored
Implement declaration emit for accessors in the js declaration emitter (#33649)
* Implement declaration emit for accessors in the js declaration emitter * Use `or`
1 parent ff5d38a commit 7f3c03d

8 files changed

+503
-17
lines changed

src/compiler/binder.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,9 +2791,38 @@ namespace ts {
27912791
(namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) :
27922792
(namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable()));
27932793

2794-
const isMethod = isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!);
2795-
const includes = isMethod ? SymbolFlags.Method : SymbolFlags.Property;
2796-
const excludes = isMethod ? SymbolFlags.MethodExcludes : SymbolFlags.PropertyExcludes;
2794+
let includes = SymbolFlags.None;
2795+
let excludes = SymbolFlags.None;
2796+
// Method-like
2797+
if (isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!)) {
2798+
includes = SymbolFlags.Method;
2799+
excludes = SymbolFlags.MethodExcludes;
2800+
}
2801+
// Maybe accessor-like
2802+
else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) {
2803+
if (some(declaration.arguments[2].properties, p => {
2804+
const id = getNameOfDeclaration(p);
2805+
return !!id && isIdentifier(id) && idText(id) === "set";
2806+
})) {
2807+
// We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this
2808+
// symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration)
2809+
includes |= SymbolFlags.SetAccessor | SymbolFlags.Property;
2810+
excludes |= SymbolFlags.SetAccessorExcludes;
2811+
}
2812+
if (some(declaration.arguments[2].properties, p => {
2813+
const id = getNameOfDeclaration(p);
2814+
return !!id && isIdentifier(id) && idText(id) === "get";
2815+
})) {
2816+
includes |= SymbolFlags.GetAccessor | SymbolFlags.Property;
2817+
excludes |= SymbolFlags.GetAccessorExcludes;
2818+
}
2819+
}
2820+
2821+
if (includes === SymbolFlags.None) {
2822+
includes = SymbolFlags.Property;
2823+
excludes = SymbolFlags.PropertyExcludes;
2824+
}
2825+
27972826
declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment);
27982827
}
27992828

src/compiler/checker.ts

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4916,8 +4916,8 @@ namespace ts {
49164916
}
49174917

49184918
function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] {
4919-
const serializePropertySymbolForClass = makeSerializePropertySymbol<ClassElement>(createProperty, SyntaxKind.MethodDeclaration);
4920-
const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol<TypeElement>((_decorators, mods, name, question, type, initializer) => createPropertySignature(mods, name, question, type, initializer), SyntaxKind.MethodSignature);
4919+
const serializePropertySymbolForClass = makeSerializePropertySymbol<ClassElement>(createProperty, SyntaxKind.MethodDeclaration, /*useAcessors*/ true);
4920+
const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol<TypeElement>((_decorators, mods, name, question, type, initializer) => createPropertySignature(mods, name, question, type, initializer), SyntaxKind.MethodSignature, /*useAcessors*/ false);
49214921

49224922
// TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of
49234923
// declaration mapping
@@ -5734,7 +5734,23 @@ namespace ts {
57345734
questionOrExclamationToken: QuestionToken | undefined,
57355735
type: TypeNode | undefined,
57365736
initializer: Expression | undefined
5737-
) => T, methodKind: SyntaxKind): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]) {
5737+
) => T, methodKind: SyntaxKind, useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]);
5738+
function makeSerializePropertySymbol<T extends Node>(createProperty: (
5739+
decorators: readonly Decorator[] | undefined,
5740+
modifiers: readonly Modifier[] | undefined,
5741+
name: string | PropertyName,
5742+
questionOrExclamationToken: QuestionToken | undefined,
5743+
type: TypeNode | undefined,
5744+
initializer: Expression | undefined
5745+
) => T, methodKind: SyntaxKind, useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]);
5746+
function makeSerializePropertySymbol<T extends Node>(createProperty: (
5747+
decorators: readonly Decorator[] | undefined,
5748+
modifiers: readonly Modifier[] | undefined,
5749+
name: string | PropertyName,
5750+
questionOrExclamationToken: QuestionToken | undefined,
5751+
type: TypeNode | undefined,
5752+
initializer: Expression | undefined
5753+
) => T, methodKind: SyntaxKind, useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) {
57385754
return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined) {
57395755
if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) {
57405756
// Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols
@@ -5750,7 +5766,40 @@ namespace ts {
57505766
const staticFlag = isStatic ? ModifierFlags.Static : 0;
57515767
const rawName = unescapeLeadingUnderscores(p.escapedName);
57525768
const name = getPropertyNameNodeForSymbolFromNameType(p, context) || createIdentifier(rawName);
5753-
if (p.flags & (SymbolFlags.Property | SymbolFlags.Accessor | SymbolFlags.Variable)) {
5769+
const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression));
5770+
if (p.flags & SymbolFlags.Accessor && useAccessors) {
5771+
const result: AccessorDeclaration[] = [];
5772+
if (p.flags & SymbolFlags.SetAccessor) {
5773+
result.push(setTextRange(createSetAccessor(
5774+
/*decorators*/ undefined,
5775+
createModifiersFromModifierFlags(staticFlag),
5776+
name,
5777+
[createParameter(
5778+
/*decorators*/ undefined,
5779+
/*modifiers*/ undefined,
5780+
/*dotDotDotToken*/ undefined,
5781+
"arg",
5782+
/*questionToken*/ undefined,
5783+
serializeTypeForDeclaration(getTypeOfSymbol(p), p)
5784+
)],
5785+
/*body*/ undefined
5786+
), find(p.declarations, isSetAccessor) || firstPropertyLikeDecl));
5787+
}
5788+
if (p.flags & SymbolFlags.GetAccessor) {
5789+
result.push(setTextRange(createGetAccessor(
5790+
/*decorators*/ undefined,
5791+
createModifiersFromModifierFlags(staticFlag),
5792+
name,
5793+
[],
5794+
serializeTypeForDeclaration(getTypeOfSymbol(p), p),
5795+
/*body*/ undefined
5796+
), find(p.declarations, isGetAccessor) || firstPropertyLikeDecl));
5797+
}
5798+
return result;
5799+
}
5800+
// This is an else/if as accessors and properties can't merge in TS, but might in JS
5801+
// If this happens, we assume the accessor takes priority, as it imposes more constraints
5802+
else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable)) {
57545803
return setTextRange(createProperty(
57555804
/*decorators*/ undefined,
57565805
createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | staticFlag),
@@ -5760,7 +5809,7 @@ namespace ts {
57605809
// TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357
57615810
// interface members can't have initializers, however class members _can_
57625811
/*initializer*/ undefined
5763-
), filter(p.declarations, d => isPropertyDeclaration(d) || isAccessor(d) || isVariableDeclaration(d) || isPropertySignature(d) || isBinaryExpression(d) || isPropertyAccessExpression(d))[0]);
5812+
), find(p.declarations, or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl);
57645813
}
57655814
if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) {
57665815
const type = getTypeOfSymbol(p);
@@ -7340,7 +7389,7 @@ namespace ts {
73407389
}
73417390
}
73427391
else {
7343-
Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function");
7392+
Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function");
73447393
errorOrSuggestion(noImplicitAny, getter!, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol));
73457394
}
73467395
return anyType;

tests/baselines/reference/jsDeclarationsClasses.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -479,18 +479,22 @@ export class E<T, U> {
479479
*/
480480
static staticReadonlyField: string;
481481
static staticInitializedField: number;
482+
/**
483+
* @param {string} _p
484+
*/
485+
static set s1(arg: string);
482486
/**
483487
* @return {string}
484488
*/
485-
static s1: string;
489+
static get s1(): string;
486490
/**
487491
* @return {string}
488492
*/
489-
static readonly s2: string;
493+
static get s2(): string;
490494
/**
491495
* @param {string} _p
492496
*/
493-
static s3: string;
497+
static set s3(arg: string);
494498
/**
495499
* @param {T} a
496500
* @param {U} b
@@ -506,18 +510,22 @@ export class E<T, U> {
506510
*/
507511
readonlyField: T & U;
508512
initializedField: number;
513+
/**
514+
* @param {U} _p
515+
*/
516+
set f1(arg: U);
509517
/**
510518
* @return {U}
511519
*/
512-
f1: U;
520+
get f1(): U;
513521
/**
514522
* @return {U}
515523
*/
516-
readonly f2: U;
524+
get f2(): U;
517525
/**
518526
* @param {U} _p
519527
*/
520-
f3: U;
528+
set f3(arg: U);
521529
}
522530
/**
523531
* @template T,U

tests/baselines/reference/jsDeclarationsFunctionLikeClasses2.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,16 @@ export class Point2D {
184184
* @param {number} y
185185
*/
186186
constructor(x: number, y: number);
187-
x: number;
188-
y: number;
187+
/**
188+
* @param {number} x
189+
*/
190+
set x(arg: number);
191+
get x(): number;
192+
/**
193+
* @param {number} y
194+
*/
195+
set y(arg: number);
196+
get y(): number;
189197
__proto__: typeof Vec;
190198
}
191199
//// [referencer.d.ts]
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//// [index.js]
2+
export class A {
3+
get x() {
4+
return 12;
5+
}
6+
}
7+
8+
export class B {
9+
/**
10+
* @param {number} _arg
11+
*/
12+
set x(_arg) {
13+
}
14+
}
15+
16+
export class C {
17+
get x() {
18+
return 12;
19+
}
20+
set x(_arg) {
21+
}
22+
}
23+
24+
export class D {}
25+
Object.defineProperty(D.prototype, "x", {
26+
get() {
27+
return 12;
28+
}
29+
});
30+
31+
export class E {}
32+
Object.defineProperty(E.prototype, "x", {
33+
/**
34+
* @param {number} _arg
35+
*/
36+
set(_arg) {}
37+
});
38+
39+
export class F {}
40+
Object.defineProperty(F.prototype, "x", {
41+
get() {
42+
return 12;
43+
},
44+
/**
45+
* @param {number} _arg
46+
*/
47+
set(_arg) {}
48+
});
49+
50+
51+
//// [index.js]
52+
export class A {
53+
get x() {
54+
return 12;
55+
}
56+
}
57+
export class B {
58+
/**
59+
* @param {number} _arg
60+
*/
61+
set x(_arg) {
62+
}
63+
}
64+
export class C {
65+
get x() {
66+
return 12;
67+
}
68+
set x(_arg) {
69+
}
70+
}
71+
export class D {
72+
}
73+
Object.defineProperty(D.prototype, "x", {
74+
get() {
75+
return 12;
76+
}
77+
});
78+
export class E {
79+
}
80+
Object.defineProperty(E.prototype, "x", {
81+
/**
82+
* @param {number} _arg
83+
*/
84+
set(_arg) { }
85+
});
86+
export class F {
87+
}
88+
Object.defineProperty(F.prototype, "x", {
89+
get() {
90+
return 12;
91+
},
92+
/**
93+
* @param {number} _arg
94+
*/
95+
set(_arg) { }
96+
});
97+
98+
99+
//// [index.d.ts]
100+
export class A {
101+
get x(): number;
102+
}
103+
export class B {
104+
/**
105+
* @param {number} _arg
106+
*/
107+
set x(arg: number);
108+
}
109+
export class C {
110+
set x(arg: number);
111+
get x(): number;
112+
}
113+
export class D {
114+
get x(): number;
115+
}
116+
export class E {
117+
set x(arg: number);
118+
}
119+
export class F {
120+
set x(arg: number);
121+
get x(): number;
122+
}

0 commit comments

Comments
 (0)