Skip to content
Closed
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
3 changes: 2 additions & 1 deletion internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ func (r *EmitResolver) IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node)
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*/)
globalSymbol := core.OrElse(r.checker.getGlobalSymbol("Symbol", ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*diagnostic*/), r.checker.unknownSymbol)
return r.checker.getResolvedSymbol(node.Expression()) == globalSymbol
}
if node.Expression().Expression().Kind != ast.KindIdentifier || node.Expression().Expression().Text() != "globalThis" || node.Expression().Name().Text() != "Symbol" {
return false
Expand Down
30 changes: 28 additions & 2 deletions internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ 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.compilerOptions)}
b.cloneBindingNameVisitor = ast.NewNodeVisitor(b.cloneBindingName, b.f, ast.NodeVisitorHooks{})
return b
}
Expand Down Expand Up @@ -1166,7 +1166,27 @@ func (b_ *NodeBuilderImpl) sortByBestName(a sortedSymbolNamePair, b sortedSymbol
// A is relative, B is non-relative: prefer B
return 1
}
return b_.ch.compareSymbols(a.sym, b.sym) // must sort symbols for stable ordering
aIsValueContainer := b_.isValueContainerForTypeOnlyContainer(a.sym, b.sym)
bIsValueContainer := b_.isValueContainerForTypeOnlyContainer(b.sym, a.sym)
if aIsValueContainer != bIsValueContainer {
if aIsValueContainer {
return -1
}
return 1
}
return b_.ch.compareSymbols(a.sym, b.sym)
}

func (b *NodeBuilderImpl) isValueContainerForTypeOnlyContainer(value *ast.Symbol, container *ast.Symbol) bool {
// Strada's stable sort preserves the variable-before-type ordering from
// getWithAlternativeContainers during declaration emit. Corsa otherwise
// uses compareSymbols to make ties deterministic.
return b.ctx.flags&nodebuilder.FlagsUseStructuralFallback != 0 &&
value.Flags&ast.SymbolFlagsValue != 0 &&
container.Flags&ast.SymbolFlagsValue == 0 &&
container.Flags&ast.SymbolFlagsType != 0 &&
b.ch.getDeclaredTypeOfSymbol(container).flags&TypeFlagsObject != 0 &&
b.ch.getTypeOfSymbol(value) == b.ch.getDeclaredTypeOfSymbol(container)
}

func canHaveModuleSpecifier(node *ast.Node) bool {
Expand Down Expand Up @@ -2246,6 +2266,12 @@ func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati
}
}
reportErrors := !b.ctx.suppressReportInferenceFallback
if reportErrors &&
requiresAddingUndefined &&
pt.Kind == pseudochecker.PseudoTypeKindDirect &&
!pseudochecker.CanAddUndefinedToTypeNode(pt.AsPseudoTypeDirect().TypeNode) {
b.ctx.tracker.ReportInferenceFallback(declaration)
}
if b.pseudoTypeEquivalentToType(pt, t, !requiresAddingUndefined && (ast.IsParameterDeclaration(declaration) || ast.IsPropertySignatureDeclaration(declaration) || ast.IsPropertyDeclaration(declaration)) && isOptionalDeclaration(declaration), reportErrors) {
// !!! TODO: If annotated type node is a reference with insufficient type arguments, we should still fall back to type serialization
// see: canReuseTypeNodeAnnotation in strada for context
Expand Down
14 changes: 12 additions & 2 deletions internal/pseudochecker/checker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// 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/binder"
"github.com/microsoft/typescript-go/internal/core"
)

// 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 @@ -14,8 +19,13 @@ package pseudochecker
type PseudoChecker struct {
strictNullChecks bool
exactOptionalPropertyTypes bool
nameResolver *binder.NameResolver
}

func NewPseudoChecker(strictNullChecks bool, exactOptionalPropertyTypes bool) *PseudoChecker {
return &PseudoChecker{strictNullChecks: strictNullChecks, exactOptionalPropertyTypes: exactOptionalPropertyTypes}
func NewPseudoChecker(strictNullChecks bool, exactOptionalPropertyTypes bool, compilerOptions *core.CompilerOptions) *PseudoChecker {
return &PseudoChecker{
strictNullChecks: strictNullChecks,
exactOptionalPropertyTypes: exactOptionalPropertyTypes,
nameResolver: &binder.NameResolver{CompilerOptions: compilerOptions},

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think it's actually quite possible to make the name resolver not have compiler options at all, but this of course goofy to have plumbed this way (for the time being)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have a working version of that idea, and it does work, though maybe a little weird.

}
}
71 changes: 69 additions & 2 deletions internal/pseudochecker/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,13 @@ func (ch *PseudoChecker) typeFromSingleReturnExpression(fn *ast.Node) *PseudoTyp
return NewPseudoTypeDirect(t)
}
} else {
return ch.typeFromExpression(candidateExpr)
result := ch.typeFromExpression(candidateExpr)
if ast.IsFunctionDeclaration(fn) &&
result.Kind == PseudoTypeKindInferred &&
len(result.AsPseudoTypeInferred().ErrorNodes) == 0 {
return NewPseudoTypeInferred(fn, true)
}
return result
}
}
return NewPseudoTypeInferred(fn, true)
Expand Down Expand Up @@ -427,14 +433,40 @@ 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) {
errorNodes = append(errorNodes, e.Name())
}
}
}
return errorNodes
}

func (ch *PseudoChecker) isDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool {
if !ast.IsPropertyAccessExpression(node) ||
!ast.IsIdentifier(node.Name()) ||
!ast.IsPropertyAccessExpression(node.Expression()) && !ast.IsIdentifier(node.Expression()) {
return false
}
if ast.IsIdentifier(node.Expression()) {
return node.Expression().Text() == "Symbol" && ch.isUnshadowedGlobalName(node.Expression())
}
return ast.IsIdentifier(node.Expression().Expression()) &&
node.Expression().Expression().Text() == "globalThis" &&
node.Expression().Name().Text() == "Symbol" &&
ch.isUnshadowedGlobalName(node.Expression().Expression())
}

func (ch *PseudoChecker) isUnshadowedGlobalName(identifier *ast.Node) bool {
return ch.nameResolver.Resolve(
identifier,
identifier.Text(),
ast.SymbolFlagsValue|ast.SymbolFlagsExportValue|ast.SymbolFlagsAlias,
nil, /*nameNotFoundMessage*/
false, /*isUse*/
true, /*excludeGlobals*/
) == nil
}

func (ch *PseudoChecker) typeFromArrayLiteral(node *ast.ArrayLiteralExpression) *PseudoType {
if errorNodes := ch.canGetTypeFromArrayLiteral(node); errorNodes != nil {
return NewPseudoTypeInferredWithErrors(node.AsNode(), false, errorNodes)
Expand Down Expand Up @@ -574,6 +606,41 @@ func typeNodeCouldReferToUndefined(node *ast.Node) bool {
}
}

func CanAddUndefinedToTypeNode(node *ast.Node) bool {
if ast.IsKeywordTypeKind(node.Kind) {
return true
}
switch node.Kind {
case ast.KindLiteralType,
ast.KindFunctionType,
ast.KindConstructorType,
ast.KindArrayType,
ast.KindTupleType,
ast.KindTypeLiteral,
ast.KindTemplateLiteralType,
ast.KindThisType:
return true
case ast.KindParenthesizedType:
return CanAddUndefinedToTypeNode(node.AsParenthesizedTypeNode().Type)
case ast.KindUnionType:
for _, t := range node.AsUnionTypeNode().Types.Nodes {
if !CanAddUndefinedToTypeNode(t) {
return false
}
}
return true
case ast.KindIntersectionType:
for _, t := range node.AsIntersectionTypeNode().Types.Nodes {
if !CanAddUndefinedToTypeNode(t) {
return false
}
}
return true
default:
return false
}
}

// see this as the inverse of `canAddUndefined` in `expressionToTypeNode` in strada
func CouldAlreadyReferToUndefinedType(t *PseudoType) bool {
if t.Kind == PseudoTypeKindNoResult || t.Kind == PseudoTypeKindInferred || isUndefinedPseudoType(t) {
Expand Down
2 changes: 1 addition & 1 deletion internal/transformers/declarations/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ func createGetIsolatedDeclarationErrors(resolver printer.EmitResolver) func(node
if ast.IsSetAccessorDeclaration(node.Parent) {
return createAccessorTypeError(node.Parent)
}
addUndefined := resolver.RequiresAddingImplicitUndefinedUnsafe(node, nil, nil) // skip checker lock - node builder will already have one
addUndefined := resolver.RequiresAddingImplicitUndefinedUnsafe(node, nil, node.Parent) // skip checker lock - node builder will already have one
if !addUndefined && node.Initializer() != nil {
return createExpressionError(node.Initializer())
}
Expand Down
6 changes: 4 additions & 2 deletions internal/transformers/declarations/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -1127,8 +1127,7 @@ func (tx *DeclarationTransformer) transformMethodDeclaration(input *ast.MethodDe
} else if ast.IsPrivateIdentifier(input.Name()) {
return nil
} else {
return tx.Factory().UpdateMethodDeclaration(
input,
result := tx.Factory().NewMethodDeclaration(
tx.ensureModifiers(input.AsNode()),
nil,
input.Name(),
Expand All @@ -1139,6 +1138,9 @@ func (tx *DeclarationTransformer) transformMethodDeclaration(input *ast.MethodDe
nil,
nil,
)
tx.EmitContext().SetOriginal(result, input.AsNode())
tx.preserveJsDoc(result, input.AsNode())
return result
}
}

Expand Down
1 change: 1 addition & 0 deletions internal/transformers/estransforms/using.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ func (tx *usingDeclarationTransformer) hoistInitializedVariable(node *ast.Variab
var target *ast.Expression
if ast.IsIdentifier(node.Name()) {
target = node.Name().Clone(tx.Factory())
target.Loc = core.UndefinedTextRange()
tx.EmitContext().SetEmitFlags(target, tx.EmitContext().EmitFlags(target) & ^(printer.EFLocalName|printer.EFExportName))
} else {
target = transformers.ConvertBindingPatternToAssignmentPattern(tx.EmitContext(), node.Name().AsBindingPattern())
Expand Down
33 changes: 19 additions & 14 deletions internal/transformers/tstransforms/runtimesyntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,15 @@ func (tx *RuntimeSyntaxTransformer) getNamespaceContainerName(node *ast.Node) *a

// Gets an expression used to refer to an export of a namespace or a member of an enum by property name.
func (tx *RuntimeSyntaxTransformer) getNamespaceQualifiedProperty(ns *ast.IdentifierNode, name *ast.IdentifierNode) *ast.Expression {
return tx.Factory().GetNamespaceMemberName(ns, name, printer.NameOptions{AllowSourceMaps: true})
location := name
if !ast.NodeIsSynthesized(name) {
name = name.Clone(tx.Factory())
name.Loc = core.UndefinedTextRange()
}
qualifiedName := tx.Factory().NewPropertyAccessExpression(ns, nil /*questionDotToken*/, name, ast.NodeFlagsNone)
tx.EmitContext().AssignCommentAndSourceMapRanges(qualifiedName, location)
tx.EmitContext().AddEmitFlags(qualifiedName, printer.EFNoComments)
return qualifiedName
}

// Gets an expression used to refer to an export of a namespace or a member of an enum by indexed access.
Expand All @@ -233,7 +241,8 @@ func (tx *RuntimeSyntaxTransformer) getNamespaceQualifiedElement(ns *ast.Identif
// Gets an expression used within the provided node's container for any exported references.
func (tx *RuntimeSyntaxTransformer) getExportQualifiedReferenceToDeclaration(node *ast.Declaration) *ast.Expression {
if tx.isExportOfNamespace(node.AsNode()) {
return tx.Factory().GetExternalModuleOrNamespaceExportName(tx.getNamespaceContainerName(tx.currentNamespace), node, false /*allowComments*/, true /*allowSourceMaps*/)
name := tx.Factory().GetDeclarationNameEx(node.AsNode(), printer.NameOptions{AllowSourceMaps: true})
return tx.getNamespaceQualifiedProperty(tx.getNamespaceContainerName(tx.currentNamespace), name)
}
return tx.Factory().GetDeclarationNameEx(node.AsNode(), printer.NameOptions{AllowSourceMaps: true})
}
Expand Down Expand Up @@ -571,7 +580,7 @@ func (tx *RuntimeSyntaxTransformer) visitImportEqualsDeclaration(node *ast.Impor
return varStatement
} else {
// exports.${name} = ${moduleReference};
statement := tx.createExportStatement(node.Name(), moduleReference, node.Loc, node.Loc, node.AsNode())
statement := tx.createExportStatement(node.Name(), moduleReference, node.Loc, node.AsNode())
statement.Loc = node.Loc
return statement
}
Expand Down Expand Up @@ -935,7 +944,7 @@ func (tx *RuntimeSyntaxTransformer) visitExpressionIdentifier(node *ast.Identifi
memberName := node.Clone(tx.Factory())
tx.EmitContext().SetEmitFlags(memberName, printer.EFNoComments|printer.EFNoSourceMap)

expression := tx.Factory().GetNamespaceMemberName(containerName, memberName, printer.NameOptions{AllowSourceMaps: true})
expression := tx.getNamespaceQualifiedProperty(containerName, memberName)
tx.EmitContext().AssignCommentAndSourceMapRanges(expression, node.AsNode())
return expression
}
Expand All @@ -944,7 +953,10 @@ func (tx *RuntimeSyntaxTransformer) visitExpressionIdentifier(node *ast.Identifi
}

func (tx *RuntimeSyntaxTransformer) createExportStatementForDeclaration(node *ast.Declaration) *ast.Statement {
exportName := tx.Factory().GetExternalModuleOrNamespaceExportName(tx.getNamespaceContainerName(tx.currentNamespace), node, false /*allowComments*/, true /*allowSourceMaps*/)
exportName := tx.getNamespaceQualifiedProperty(
tx.getNamespaceContainerName(tx.currentNamespace),
tx.Factory().GetDeclarationNameEx(node, printer.NameOptions{AllowSourceMaps: true}),
)
localName := tx.Factory().GetLocalName(node)
expression := tx.Factory().NewAssignmentExpression(exportName, localName)
exportAssignmentSourceMapRange := node.Loc
Expand All @@ -959,16 +971,9 @@ func (tx *RuntimeSyntaxTransformer) createExportStatementForDeclaration(node *as
return statement
}

func (tx *RuntimeSyntaxTransformer) createExportAssignment(name *ast.IdentifierNode, expression *ast.Expression, exportAssignmentSourceMapRange core.TextRange, original *ast.Node) *ast.Expression {
func (tx *RuntimeSyntaxTransformer) createExportStatement(name *ast.IdentifierNode, expression *ast.Expression, exportStatementSourceMapRange core.TextRange, original *ast.Node) *ast.Statement {
exportName := tx.getNamespaceQualifiedProperty(tx.getNamespaceContainerName(tx.currentNamespace), name)
exportAssignment := tx.Factory().NewAssignmentExpression(exportName, expression)
tx.EmitContext().SetOriginal(exportAssignment, original)
tx.EmitContext().SetSourceMapRange(exportAssignment, exportAssignmentSourceMapRange)
return exportAssignment
}

func (tx *RuntimeSyntaxTransformer) createExportStatement(name *ast.IdentifierNode, expression *ast.Expression, exportAssignmentSourceMapRange core.TextRange, exportStatementSourceMapRange core.TextRange, original *ast.Node) *ast.Statement {
exportStatement := tx.Factory().NewExpressionStatement(tx.createExportAssignment(name, expression, exportAssignmentSourceMapRange, original))
exportStatement := tx.Factory().NewExpressionStatement(tx.Factory().NewAssignmentExpression(exportName, expression))
tx.EmitContext().SetOriginal(exportStatement, original)
tx.EmitContext().SetSourceMapRange(exportStatement, exportStatementSourceMapRange)
return exportStatement
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
aliased.ts(4,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
shadowed.ts(6,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.


==== global.ts (0 errors) ====
export const globalSymbol = {
[Symbol.iterator]: 1,
};

==== shadowed.ts (1 errors) ====
const Symbol = {
iterator: "iterator",
};

export const shadowedSymbol = {
[Symbol.iterator]: 1,
~~~~~~~~~~~~~~~~~
!!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
!!! related TS9027 shadowed.ts:5:14: Add a type annotation to the variable shadowedSymbol.
};

==== symbols.ts (0 errors) ====
export const symbolValue = {
iterator: "iterator",
};

==== aliased.ts (1 errors) ====
import { symbolValue as Symbol } from "./symbols.js";

export const aliasedSymbol = {
[Symbol.iterator]: 1,
~~~~~~~~~~~~~~~~~
!!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
!!! related TS9027 aliased.ts:3:14: Add a type annotation to the variable aliasedSymbol.
};

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

//// [global.ts]
export const globalSymbol = {
[Symbol.iterator]: 1,
};

//// [shadowed.ts]
const Symbol = {
iterator: "iterator",
};

export const shadowedSymbol = {
[Symbol.iterator]: 1,
};

//// [symbols.ts]
export const symbolValue = {
iterator: "iterator",
};

//// [aliased.ts]
import { symbolValue as Symbol } from "./symbols.js";

export const aliasedSymbol = {
[Symbol.iterator]: 1,
};




//// [global.d.ts]
export declare const globalSymbol: {
[Symbol.iterator]: number;
};
//// [symbols.d.ts]
export declare const symbolValue: {
iterator: string;
};
Loading