Skip to content
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
20 changes: 20 additions & 0 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13454,6 +13454,26 @@ func (c *Checker) getResolvedSymbolNoDiagnostics(node *ast.Node) *ast.Symbol {
return links.resolvedSymbolNoDiagnostics
}

func (c *Checker) isDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool {
if !ast.IsPropertyAccessExpression(node) ||
!ast.IsIdentifier(node.Name()) ||
(!ast.IsPropertyAccessExpression(node.Expression()) && !ast.IsIdentifier(node.Expression())) {
return false
}
if node.Expression().Kind == ast.KindIdentifier {
if node.Expression().Text() != "Symbol" {
return false
}
// Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol.
return c.getResolvedSymbol(node.Expression()) == c.getGlobalSymbol("Symbol", ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*diagnostic*/)
}
if node.Expression().Expression().Kind != ast.KindIdentifier || node.Expression().Expression().Text() != "globalThis" || node.Expression().Name().Text() != "Symbol" {
return false
}
// Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis`.
return c.getResolvedSymbol(node.Expression().Expression()) == c.globalThisSymbol
}

func (c *Checker) getCannotFindNameDiagnosticForName(node *ast.Node) *diagnostics.Message {
switch node.Text() {
case "document", "console":
Expand Down
20 changes: 1 addition & 19 deletions internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,27 +514,9 @@ func (r *EmitResolver) IsImportRequiredByAugmentation(decl *ast.ImportDeclaratio
}

func (r *EmitResolver) IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool {
if !ast.IsPropertyAccessExpression(node) ||
!ast.IsIdentifier(node.Name()) ||
!ast.IsPropertyAccessExpression(node.Expression()) && !ast.IsIdentifier(node.Expression()) {
return false
}
if node.Expression().Kind == ast.KindIdentifier {
if node.Expression().Text() != "Symbol" {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
// Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol
return r.checker.getResolvedSymbol(node.Expression()) == r.checker.getGlobalSymbol("Symbol", ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*diagnostic*/)
}
if node.Expression().Expression().Kind != ast.KindIdentifier || node.Expression().Expression().Text() != "globalThis" || node.Expression().Name().Text() != "Symbol" {
return false
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
// Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis`
return r.checker.getResolvedSymbol(node.Expression().Expression()) == r.checker.globalThisSymbol
return r.checker.isDefinitelyReferenceToGlobalSymbolObject(node)
}

func (r *EmitResolver) RequiresAddingImplicitUndefined(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
Expand Down
12 changes: 11 additions & 1 deletion internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,17 @@ func newNodeBuilderImpl(ch *Checker, e *printer.EmitContext, idToSymbol map[*ast
if idToSymbol == nil {
idToSymbol = make(map[*ast.IdentifierNode]*ast.Symbol)
}
b := &NodeBuilderImpl{f: e.Factory.AsNodeFactory(), ch: ch, e: e, idToSymbol: idToSymbol, pc: pseudochecker.NewPseudoChecker(ch.strictNullChecks, ch.exactOptionalPropertyTypes)}
b := &NodeBuilderImpl{
f: e.Factory.AsNodeFactory(),
ch: ch,
e: e,
idToSymbol: idToSymbol,
pc: pseudochecker.NewPseudoChecker(
ch.strictNullChecks,
ch.exactOptionalPropertyTypes,
ch.isDefinitelyReferenceToGlobalSymbolObject,
),
}
b.cloneBindingNameVisitor = ast.NewNodeVisitor(b.cloneBindingName, b.f, ast.NodeVisitorHooks{})
return b
}
Expand Down
15 changes: 11 additions & 4 deletions internal/pseudochecker/checker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// pseudochecker is a limited "checker" that returns pseudo-"types" of expressions - mostly those which trivially have type nodes
package pseudochecker

import "github.com/microsoft/typescript-go/internal/ast"

// TODO: Late binding/symbol merging?
// In strada, `expressionToTypeNode` used many `resolver` methods whose net effect was just
// calling `Checker.GetMergedSymbol` on a symbol when dealing with accessors. Right now those
Expand All @@ -12,10 +14,15 @@ package pseudochecker
// extracting the `mergeSymbol` core checker logic into a reusable component.

type PseudoChecker struct {
strictNullChecks bool
exactOptionalPropertyTypes bool
strictNullChecks bool
exactOptionalPropertyTypes bool
isDefinitelyReferenceToGlobalSymbolObject func(node *ast.Node) bool
}

func NewPseudoChecker(strictNullChecks bool, exactOptionalPropertyTypes bool) *PseudoChecker {
return &PseudoChecker{strictNullChecks: strictNullChecks, exactOptionalPropertyTypes: exactOptionalPropertyTypes}
func NewPseudoChecker(strictNullChecks bool, exactOptionalPropertyTypes bool, isDefinitelyReferenceToGlobalSymbolObject func(node *ast.Node) bool) *PseudoChecker {
return &PseudoChecker{
strictNullChecks: strictNullChecks,
exactOptionalPropertyTypes: exactOptionalPropertyTypes,
isDefinitelyReferenceToGlobalSymbolObject: isDefinitelyReferenceToGlobalSymbolObject,
}
}
3 changes: 2 additions & 1 deletion internal/pseudochecker/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,8 @@ func (ch *PseudoChecker) canGetTypeFromObjectLiteral(node *ast.ObjectLiteralExpr
}
if e.Name().Kind == ast.KindComputedPropertyName {
expression := e.Name().Expression()
if !ast.IsPrimitiveLiteralValue(expression, false) {
if !ast.IsPrimitiveLiteralValue(expression, false) &&
!ch.isDefinitelyReferenceToGlobalSymbolObject(expression) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super fond of coupling the pseudochecker with a checker's method like this but this is kinda the earliest moment at which this thing can be checked without doubling the work somewhere else.

I tried putting this in the nodebuilder but that had its own issues and was allowing all computed properties in the pseudochecker, just so the nodebuilder could reject them

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go so far as to say it's a bad idea that goes against the premise of the ID pseudochecker. The functionality needs to be implementable without a full check to be consistent with ID's goals.

Copy link
Copy Markdown
Member

@weswigham weswigham Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fortunately, it seems like isDefinitelyReferenceToGlobalSymbolObject seems like it's not too bad to implement an equivalent outside the checker? You just need the binder's reference resolver instead.

return false
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [tests/cases/compiler/declarationEmitComputedPropertyGlobalSymbol1.ts] ////

//// [declarationEmitComputedPropertyGlobalSymbol1.ts]
export const symbolNamed = {
[Symbol.toStringTag]: "demo",
[Symbol.iterator](): IterableIterator<number> {
return [1, 2, 3][Symbol.iterator]();
},
} as const;


//// [declarationEmitComputedPropertyGlobalSymbol1.js]
export const symbolNamed = {
[Symbol.toStringTag]: "demo",
[Symbol.iterator]() {
return [1, 2, 3][Symbol.iterator]();
},
};


//// [declarationEmitComputedPropertyGlobalSymbol1.d.ts]
export declare const symbolNamed: {
readonly [Symbol.toStringTag]: "demo";
readonly [Symbol.iterator]: () => IterableIterator<number>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [tests/cases/compiler/declarationEmitComputedPropertyGlobalSymbol1.ts] ////

=== declarationEmitComputedPropertyGlobalSymbol1.ts ===
export const symbolNamed = {
>symbolNamed : Symbol(symbolNamed, Decl(declarationEmitComputedPropertyGlobalSymbol1.ts, 0, 12))

[Symbol.toStringTag]: "demo",
>[Symbol.toStringTag] : Symbol([Symbol.toStringTag], Decl(declarationEmitComputedPropertyGlobalSymbol1.ts, 0, 28))
>Symbol.toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --))

[Symbol.iterator](): IterableIterator<number> {
>[Symbol.iterator] : Symbol([Symbol.iterator], Decl(declarationEmitComputedPropertyGlobalSymbol1.ts, 1, 33))
>Symbol.iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))
>IterableIterator : Symbol(IterableIterator, Decl(lib.es2015.iterable.d.ts, --, --))

return [1, 2, 3][Symbol.iterator]();
>Symbol.iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))

},
} as const;
>const : Symbol(const)

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//// [tests/cases/compiler/declarationEmitComputedPropertyGlobalSymbol1.ts] ////

=== declarationEmitComputedPropertyGlobalSymbol1.ts ===
export const symbolNamed = {
>symbolNamed : { readonly [Symbol.toStringTag]: "demo"; readonly [Symbol.iterator]: () => IterableIterator<number>; }
>{ [Symbol.toStringTag]: "demo", [Symbol.iterator](): IterableIterator<number> { return [1, 2, 3][Symbol.iterator](); },} as const : { readonly [Symbol.toStringTag]: "demo"; readonly [Symbol.iterator]: () => IterableIterator<number>; }
>{ [Symbol.toStringTag]: "demo", [Symbol.iterator](): IterableIterator<number> { return [1, 2, 3][Symbol.iterator](); },} : { readonly [Symbol.toStringTag]: "demo"; readonly [Symbol.iterator]: () => IterableIterator<number>; }

[Symbol.toStringTag]: "demo",
>[Symbol.toStringTag] : "demo"
>Symbol.toStringTag : unique symbol
>Symbol : SymbolConstructor
>toStringTag : unique symbol
>"demo" : "demo"

[Symbol.iterator](): IterableIterator<number> {
>[Symbol.iterator] : () => IterableIterator<number>
>Symbol.iterator : unique symbol
>Symbol : SymbolConstructor
>iterator : unique symbol

return [1, 2, 3][Symbol.iterator]();
>[1, 2, 3][Symbol.iterator]() : ArrayIterator<number>
>[1, 2, 3][Symbol.iterator] : () => ArrayIterator<number>
>[1, 2, 3] : number[]
>1 : 1
>2 : 2
>3 : 3
>Symbol.iterator : unique symbol
>Symbol : SymbolConstructor
>iterator : unique symbol

},
} as const;

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
declarationEmitComputedPropertyGlobalSymbol2.ts(4,16): error TS9013: Expression type can't be inferred with --isolatedDeclarations.


==== declarationEmitComputedPropertyGlobalSymbol2.ts (1 errors) ====
export const symbolNamed = {
[Symbol.toStringTag]: "demo",
[Symbol.iterator]() {
return [1, 2, 3][Symbol.iterator]();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Strada, this reports a slightly different error (TS playground):

Method must have an explicit return type annotation with --isolatedDeclarations.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS9013: Expression type can't be inferred with --isolatedDeclarations.
!!! related TS9034 declarationEmitComputedPropertyGlobalSymbol2.ts:3:5: Add a return type to the method
!!! related TS9035 declarationEmitComputedPropertyGlobalSymbol2.ts:4:16: Add satisfies and a type assertion to this expression (satisfies T as T) to make the type explicit.
},
} as const;

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [tests/cases/compiler/declarationEmitComputedPropertyGlobalSymbol2.ts] ////

//// [declarationEmitComputedPropertyGlobalSymbol2.ts]
export const symbolNamed = {
[Symbol.toStringTag]: "demo",
[Symbol.iterator]() {
return [1, 2, 3][Symbol.iterator]();
},
} as const;


//// [declarationEmitComputedPropertyGlobalSymbol2.js]
export const symbolNamed = {
[Symbol.toStringTag]: "demo",
[Symbol.iterator]() {
return [1, 2, 3][Symbol.iterator]();
},
};


//// [declarationEmitComputedPropertyGlobalSymbol2.d.ts]
export declare const symbolNamed: {
readonly [Symbol.toStringTag]: "demo";
readonly [Symbol.iterator]: () => ArrayIterator<number>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//// [tests/cases/compiler/declarationEmitComputedPropertyGlobalSymbol2.ts] ////

=== declarationEmitComputedPropertyGlobalSymbol2.ts ===
export const symbolNamed = {
>symbolNamed : Symbol(symbolNamed, Decl(declarationEmitComputedPropertyGlobalSymbol2.ts, 0, 12))

[Symbol.toStringTag]: "demo",
>[Symbol.toStringTag] : Symbol([Symbol.toStringTag], Decl(declarationEmitComputedPropertyGlobalSymbol2.ts, 0, 28))
>Symbol.toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --))

[Symbol.iterator]() {
>[Symbol.iterator] : Symbol([Symbol.iterator], Decl(declarationEmitComputedPropertyGlobalSymbol2.ts, 1, 33))
>Symbol.iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))

return [1, 2, 3][Symbol.iterator]();
>Symbol.iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --))

},
} as const;
>const : Symbol(const)

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//// [tests/cases/compiler/declarationEmitComputedPropertyGlobalSymbol2.ts] ////

=== declarationEmitComputedPropertyGlobalSymbol2.ts ===
export const symbolNamed = {
>symbolNamed : { readonly [Symbol.toStringTag]: "demo"; readonly [Symbol.iterator]: () => ArrayIterator<number>; }
>{ [Symbol.toStringTag]: "demo", [Symbol.iterator]() { return [1, 2, 3][Symbol.iterator](); },} as const : { readonly [Symbol.toStringTag]: "demo"; readonly [Symbol.iterator]: () => ArrayIterator<number>; }
>{ [Symbol.toStringTag]: "demo", [Symbol.iterator]() { return [1, 2, 3][Symbol.iterator](); },} : { readonly [Symbol.toStringTag]: "demo"; readonly [Symbol.iterator]: () => ArrayIterator<number>; }

[Symbol.toStringTag]: "demo",
>[Symbol.toStringTag] : "demo"
>Symbol.toStringTag : unique symbol
>Symbol : SymbolConstructor
>toStringTag : unique symbol
>"demo" : "demo"

[Symbol.iterator]() {
>[Symbol.iterator] : () => ArrayIterator<number>
>Symbol.iterator : unique symbol
>Symbol : SymbolConstructor
>iterator : unique symbol

return [1, 2, 3][Symbol.iterator]();
>[1, 2, 3][Symbol.iterator]() : ArrayIterator<number>
>[1, 2, 3][Symbol.iterator] : () => ArrayIterator<number>
>[1, 2, 3] : number[]
>1 : 1
>2 : 2
>3 : 3
>Symbol.iterator : unique symbol
>Symbol : SymbolConstructor
>iterator : unique symbol

},
} as const;

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @declaration: true
// @isolatedDeclarations: true
// @target: es2015

export const symbolNamed = {
[Symbol.toStringTag]: "demo",
[Symbol.iterator](): IterableIterator<number> {
return [1, 2, 3][Symbol.iterator]();
},
} as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @declaration: true
// @isolatedDeclarations: true
// @target: es2015

export const symbolNamed = {
[Symbol.toStringTag]: "demo",
[Symbol.iterator]() {
return [1, 2, 3][Symbol.iterator]();
},
} as const;
Loading