Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bf9faa8
Port core ID type node lookup logic as the psuedochecker
weswigham Aug 21, 2025
6ed2e40
Stitch together psuedochecker and nodebuilder
weswigham Sep 8, 2025
982747d
panic fixes
weswigham Sep 8, 2025
33eaa8f
purge one last test panic
weswigham Sep 8, 2025
d24f95b
mostly complete impl, panic-free, just logical bugfixes needed
weswigham Oct 22, 2025
e2ee460
Fix nested param name preservation
weswigham Oct 22, 2025
cb35284
More complete psuedo-accessor-type ID inference to better match strad…
weswigham Oct 23, 2025
1e769c5
Non-expression positions need to fallback to declaration types rather…
weswigham Oct 23, 2025
c510a1c
More correct fallback behaviors for declaration types
weswigham Oct 23, 2025
21a3832
Rework const context handling to defer psuedotype selection until che…
weswigham Oct 23, 2025
8743302
Merge branch 'main' into isolated-modules-related
weswigham Oct 23, 2025
fd1f7a7
Fix printback for parsed mapped type nodes to not have doubled-up whi…
weswigham Oct 23, 2025
ca7ce2d
Fix local symbol tracking in reused nodes
weswigham Oct 23, 2025
a2363c9
Merge branch 'main' into isolated-modules-related
weswigham Oct 28, 2025
596d39a
Merge branch 'main' into isolated-modules-related
weswigham Jan 8, 2026
9c7376b
Fix pervasive misspelling and other lints
weswigham Jan 8, 2026
e4bdf94
Make psuedo-data members private again
weswigham Jan 8, 2026
26ce2c9
Fix missing kind crash post-merge
weswigham Jan 8, 2026
a3539c2
Fix nonascii quickinfo text with revised node copy in place
weswigham Jan 8, 2026
a121d4a
Add bail on mimatched type/pseudotype pairs - needs more implementati…
weswigham Jan 9, 2026
546794c
Fourslash changes from reused nodes
weswigham Jan 9, 2026
397d6b3
Fix vis lint
weswigham Jan 9, 2026
05140dd
Baseline updates (tentative, under review)
weswigham Jan 9, 2026
1ffe3d7
Fourslash regen
weswigham Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
37 changes: 37 additions & 0 deletions internal/ast/functionflags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ast

type FunctionFlags uint32

const (
FunctionFlagsNormal FunctionFlags = 0
FunctionFlagsGenerator FunctionFlags = 1 << 0
FunctionFlagsAsync FunctionFlags = 1 << 1
FunctionFlagsInvalid FunctionFlags = 1 << 2
FunctionFlagsAsyncGenerator FunctionFlags = FunctionFlagsAsync | FunctionFlagsGenerator
)

func GetFunctionFlags(node *Node) FunctionFlags {
if node == nil {
return FunctionFlagsInvalid
}
data := node.BodyData()
if data == nil {
return FunctionFlagsInvalid
}
flags := FunctionFlagsNormal
switch node.Kind {
case KindFunctionDeclaration, KindFunctionExpression, KindMethodDeclaration:
if data.AsteriskToken != nil {
flags |= FunctionFlagsGenerator
}
fallthrough
case KindArrowFunction:
if HasSyntacticModifier(node, ModifierFlagsAsync) {
flags |= FunctionFlagsAsync
}
}
if data.Body == nil {
flags |= FunctionFlagsInvalid
}
return flags
}
48 changes: 48 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3859,6 +3859,54 @@ func IsImportOrImportEqualsDeclaration(node *Node) bool {
return IsImportDeclaration(node) || IsImportEqualsDeclaration(node)
}

func IsPrimitiveLiteralValue(node *Node, includeBigInt bool) bool {
switch node.Kind {
case KindTrueKeyword,
KindFalseKeyword,
KindNumericLiteral,
KindStringLiteral,
KindNoSubstitutionTemplateLiteral:
return true
case KindBigIntLiteral:
return includeBigInt
case KindPrefixUnaryExpression:
if node.AsPrefixUnaryExpression().Operator == KindMinusToken {
return IsNumericLiteral(node.AsPrefixUnaryExpression().Operand) || (includeBigInt && IsBigIntLiteral(node.AsPrefixUnaryExpression().Operand))
}
if node.AsPrefixUnaryExpression().Operator == KindPlusToken {
return IsNumericLiteral(node.AsPrefixUnaryExpression().Operand)
}
return false
default:
return false
}
}

func HasInferredType(node *Node) bool {
// Debug.type<HasInferredType>(node); // !!!
switch node.Kind {
case KindParameter,
KindPropertySignature,
KindPropertyDeclaration,
KindBindingElement,
KindPropertyAccessExpression,
KindElementAccessExpression,
KindBinaryExpression,
KindVariableDeclaration,
KindExportAssignment,
KindJSExportAssignment,
KindPropertyAssignment,
KindShorthandPropertyAssignment,
KindJSDocParameterTag,
KindJSDocPropertyTag,
KindCommonJSExport:
return true
default:
// assertType<never>(node); // !!!
return false
}
}

func IsKeyword(token Kind) bool {
return KindFirstKeyword <= token && token <= KindLastKeyword
}
Expand Down
127 changes: 76 additions & 51 deletions internal/checker/checker.go

Large diffs are not rendered by default.

18 changes: 1 addition & 17 deletions internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,25 +803,9 @@ func (r *EmitResolver) GetExternalModuleFileFromDeclaration(declaration *ast.Nod
return nil
}

var specifier *ast.Node
if declaration.Kind == ast.KindModuleDeclaration {
if ast.IsStringLiteral(declaration.Name()) {
specifier = declaration.Name()
}
} else {
specifier = ast.GetExternalModuleName(declaration)
}
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
moduleSymbol := r.checker.resolveExternalModuleNameWorker(specifier, specifier /*moduleNotFoundError*/, nil, false, false) // TODO: GH#18217
if moduleSymbol == nil {
return nil
}
decl := ast.GetDeclarationOfKind(moduleSymbol, ast.KindSourceFile)
if decl == nil {
return nil
}
return decl.AsSourceFile()
return r.checker.getExternalModuleFileFromDeclaration(declaration)
}

func (r *EmitResolver) getReferenceResolver() binder.ReferenceResolver {
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/grammarchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ func (c *Checker) checkGrammarForInOrForOfStatement(forInOrOfStatement *ast.ForI
diagnostic := createDiagnosticForNode(forInOrOfStatement.AwaitModifier, diagnostics.X_for_await_loops_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules)
containingFunc := ast.GetContainingFunction(forInOrOfStatement.AsNode())
if containingFunc != nil && containingFunc.Kind != ast.KindConstructor {
debug.Assert((getFunctionFlags(containingFunc)&FunctionFlagsAsync) == 0, "Enclosing function should never be an async function.")
debug.Assert((ast.GetFunctionFlags(containingFunc)&ast.FunctionFlagsAsync) == 0, "Enclosing function should never be an async function.")
if hasAsyncModifier(containingFunc) {
panic("Enclosing function should never be an async function.")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/nodebuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (b *NodeBuilder) SerializeTypeParametersForSignature(signatureDeclaration *
// SerializeTypeForDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeTypeForDeclaration(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.serializeTypeForDeclaration(declaration, nil, symbol))
return b.exitContext(b.impl.serializeTypeForDeclaration(declaration, nil, symbol, true))
}

// SerializeTypeForExpression implements NodeBuilderInterface.
Expand Down
64 changes: 50 additions & 14 deletions internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/nodebuilder"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/pseudochecker"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
"github.com/microsoft/typescript-go/internal/tspath"
Expand Down Expand Up @@ -88,6 +89,7 @@ type NodeBuilderImpl struct {
f *ast.NodeFactory
ch *Checker
e *printer.EmitContext
pc *pseudochecker.PseudoChecker

// cache
links core.LinkStore[*ast.Node, NodeBuilderLinks]
Expand All @@ -114,7 +116,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}
b := &NodeBuilderImpl{f: e.Factory.AsNodeFactory(), ch: ch, e: e, idToSymbol: idToSymbol, pc: pseudochecker.NewPseudoChecker()}
b.cloneBindingNameVisitor = ast.NewNodeVisitor(b.cloneBindingName, b.f, ast.NodeVisitorHooks{})
return b
}
Expand Down Expand Up @@ -340,10 +342,6 @@ func (b *NodeBuilderImpl) setCommentRange(node *ast.Node, range_ *ast.Node) {
}
}

func (b *NodeBuilderImpl) tryReuseExistingTypeNodeHelper(existing *ast.TypeNode) *ast.TypeNode {
return nil // !!!
}

func (b *NodeBuilderImpl) tryReuseExistingTypeNode(typeNode *ast.TypeNode, t *Type, host *ast.Node, addUndefined bool) *ast.TypeNode {
originalType := t
if addUndefined {
Expand Down Expand Up @@ -418,7 +416,7 @@ func (b *NodeBuilderImpl) tryReuseExistingNonParameterTypeNode(existing *ast.Typ
annotationType = b.getTypeFromTypeNode(existing, true)
}
if annotationType != nil && b.typeNodeIsEquivalentToType(host, t, annotationType) && b.existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, t) {
result := b.tryReuseExistingTypeNodeHelper(existing)
result := b.tryReuseExistingNodeHelper(existing)
if result != nil {
return result
}
Expand Down Expand Up @@ -1259,7 +1257,7 @@ func (b *NodeBuilderImpl) setTextRange(range_ *ast.Node, location *ast.Node) *as
}
if !ast.NodeIsSynthesized(range_) || (range_.Flags&ast.NodeFlagsSynthesized == 0) || b.ctx.enclosingFile == nil || b.ctx.enclosingFile != ast.GetSourceFileOfNode(b.e.MostOriginal(range_)) {
original := range_
range_ = range_.Clone(b.f) // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions
range_ = b.f.DeepCloneNode(range_) // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions, deep clone so inner nodes have positions stripped
if symbol, ok := b.idToSymbol[original]; ok {
b.idToSymbol[range_] = symbol
}
Expand Down Expand Up @@ -1503,7 +1501,7 @@ func (b *NodeBuilderImpl) typeToTypeNodeHelperWithPossibleReusableTypeNode(t *Ty
return b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
}
if typeNode != nil && b.getTypeFromTypeNode(typeNode, false) == t {
reused := b.tryReuseExistingTypeNodeHelper(typeNode)
reused := b.tryReuseExistingNodeHelper(typeNode)
if reused != nil {
return reused
}
Expand Down Expand Up @@ -1558,7 +1556,7 @@ func (b *NodeBuilderImpl) symbolToParameterDeclaration(parameterSymbol *ast.Symb
parameterDeclaration := getEffectiveParameterDeclaration(parameterSymbol)

parameterType := b.ch.getTypeOfSymbol(parameterSymbol)
parameterTypeNode := b.serializeTypeForDeclaration(parameterDeclaration, parameterType, parameterSymbol)
parameterTypeNode := b.serializeTypeForDeclaration(parameterDeclaration, parameterType, parameterSymbol, true)
var modifiers *ast.ModifierList
if b.ctx.flags&nodebuilder.FlagsOmitParameterModifiers == 0 && preserveModifierFlags && parameterDeclaration != nil && ast.CanHaveModifiers(parameterDeclaration) {
originals := core.Filter(parameterDeclaration.ModifierNodes(), ast.IsModifier)
Expand Down Expand Up @@ -1989,8 +1987,16 @@ func (b *NodeBuilderImpl) indexInfoToIndexSignatureDeclarationHelper(indexInfo *
* @param type - The type to write; an existing annotation must match this type if it's used, otherwise this is the type serialized as a new type node
* @param symbol - The symbol is used both to find an existing annotation if declaration is not provided, and to determine if `unique symbol` should be printed
*/
func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declaration, t *Type, symbol *ast.Symbol) *ast.Node {
// !!! node reuse logic
func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declaration, t *Type, symbol *ast.Symbol, tryReuse bool) *ast.Node {
if declaration == nil {
if symbol != nil {
declaration = symbol.ValueDeclaration
if declaration == nil {
// TODO: prefer annotated declarations like in strada (but does this ever even matter in practice? All callers should supply a declaration!)
declaration = core.FirstOrNil(symbol.Declarations)
}
}
}
if symbol == nil {
symbol = b.ch.getSymbolOfDeclaration(declaration)
}
Expand Down Expand Up @@ -2019,8 +2025,31 @@ func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati
})) {
b.ctx.flags |= nodebuilder.FlagsAllowUniqueESSymbolType
}
result := b.typeToTypeNode(t) // !!! expressionOrTypeToTypeNode
var result *ast.Node
// !!! expandable hover support
if tryReuse && declaration != nil && (ast.IsAccessor(declaration) || (ast.HasInferredType(declaration) && !ast.NodeIsSynthesized(declaration) && (t.ObjectFlags()&ObjectFlagsRequiresWidening) == 0)) {
remove := b.addSymbolTypeToContext(symbol, t)
var pt *pseudochecker.PseudoType
if ast.IsAccessor(declaration) {
pt = b.pc.GetTypeOfAccessor(declaration)
} else {
pt = b.pc.GetTypeOfDeclaration(declaration)
}
annotated := b.pseudoTypeToType(pt)
if annotated == t || annotated != nil && b.ch.isErrorType(annotated) {
// !!! 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
result = b.pseudoTypeToNode(pt)
}
remove()
}
if result == nil {
result = b.typeToTypeNode(t)
}
restoreFlags()
if result == nil {
return b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
}
return result
}

Expand Down Expand Up @@ -2292,7 +2321,7 @@ func (b *NodeBuilderImpl) addPropertyToElementList(propertySymbol *ast.Symbol, t
b.ctx.reverseMappedStack = append(b.ctx.reverseMappedStack, propertySymbol)
}
if propertyType != nil {
propertyTypeNode = b.serializeTypeForDeclaration(nil /*declaration*/, propertyType, propertySymbol)
propertyTypeNode = b.serializeTypeForDeclaration(nil /*declaration*/, propertyType, propertySymbol, true)
} else {
propertyTypeNode = b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
}
Expand Down Expand Up @@ -2474,7 +2503,14 @@ func (b *NodeBuilderImpl) createAnonymousTypeNode(t *Type) *ast.TypeNode {
if isInstantiationExpressionType {
instantiationExpressionType := t.AsInstantiationExpressionType()
existing := instantiationExpressionType.node
if ast.IsTypeQueryNode(existing) {
// instantiationExpressionType.node is unreliable for constituents of unions and intersections.
// declare const Err: typeof ErrImpl & (<T>() => T);
// type ErrAlias<U> = typeof Err<U>;
// declare const e: ErrAlias<number>;
// ErrAlias<number> = typeof Err<number> = typeof ErrImpl & (<number>() => number)
// The problem is each constituent of the intersection will be associated with typeof Err<number>
// And when extracting a type for typeof ErrImpl from typeof Err<number> does not make sense.
if ast.IsTypeQueryNode(existing) && b.getTypeFromTypeNode(existing, false) == t {
typeNode := b.tryReuseExistingNonParameterTypeNode(existing, t, nil, nil)
if typeNode != nil {
return typeNode
Expand Down
13 changes: 13 additions & 0 deletions internal/checker/nodebuilderscopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ type localsRecord struct {
oldSymbol *ast.Symbol
}

func (b *NodeBuilderImpl) addSymbolTypeToContext(symbol *ast.Symbol, t *Type) func() {
id := ast.GetSymbolId(symbol)
oldType, oldTypeExists := b.ctx.enclosingSymbolTypes[id]
b.ctx.enclosingSymbolTypes[id] = t
return func() {
if oldTypeExists {
b.ctx.enclosingSymbolTypes[id] = oldType
} else {
delete(b.ctx.enclosingSymbolTypes, id)
}
}
}

func (b *NodeBuilderImpl) enterNewScope(declaration *ast.Node, expandedParams []*ast.Symbol, typeParameters []*Type, originalParameters []*ast.Symbol, mapper *TypeMapper) func() {
cleanupContext := cloneNodeBuilderContext(b.ctx)
// For regular function/method declarations, the enclosing declaration will already be signature.declaration,
Expand Down
Loading
Loading