From 82979b10381c68e10afc6de8d8417c945f647ccb Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 21 Aug 2025 15:55:49 -0700 Subject: [PATCH 1/4] Port core ID type node lookup logic as the psuedochecker --- internal/ast/functionflags.go | 37 ++ internal/ast/utilities.go | 73 +++ internal/checker/checker.go | 152 ++--- internal/checker/grammarchecks.go | 4 +- internal/checker/nodebuilderimpl.go | 9 +- internal/checker/relater.go | 2 +- internal/checker/utilities.go | 36 -- internal/psuedochecker/checker.go | 18 + internal/psuedochecker/lookup.go | 520 ++++++++++++++++++ internal/psuedochecker/type.go | 273 +++++++++ .../transformers/declarations/transform.go | 2 +- internal/transformers/declarations/util.go | 25 - 12 files changed, 986 insertions(+), 165 deletions(-) create mode 100644 internal/ast/functionflags.go create mode 100644 internal/psuedochecker/checker.go create mode 100644 internal/psuedochecker/lookup.go create mode 100644 internal/psuedochecker/type.go diff --git a/internal/ast/functionflags.go b/internal/ast/functionflags.go new file mode 100644 index 0000000000..431d40b585 --- /dev/null +++ b/internal/ast/functionflags.go @@ -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 +} diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 199b547521..9839df3059 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -3867,3 +3867,76 @@ func GetRestIndicatorOfBindingOrAssignmentElement(bindingElement *Node) *Node { } return nil } + +type AllAccessorDeclarations struct { + FirstAccessor *AccessorDeclaration + SecondAccessor *AccessorDeclaration + SetAccessor *SetAccessorDeclaration + GetAccessor *GetAccessorDeclaration +} + +func GetAllAccessorDeclarationsForDeclaration(accessor *AccessorDeclaration, symbol *Symbol) AllAccessorDeclarations { + var otherKind Kind + if accessor.Kind == KindSetAccessor { + otherKind = KindGetAccessor + } else if accessor.Kind == KindGetAccessor { + otherKind = KindSetAccessor + } else { + panic(fmt.Sprintf("Unexpected node kind %q", accessor.Kind)) + } + otherAccessor := GetDeclarationOfKind(symbol, otherKind) + + var firstAccessor *AccessorDeclaration + var secondAccessor *AccessorDeclaration + if otherAccessor != nil && (otherAccessor.Pos() < accessor.Pos()) { + firstAccessor = otherAccessor + secondAccessor = accessor + } else { + firstAccessor = accessor + secondAccessor = otherAccessor + } + + var setAccessor *SetAccessorDeclaration + var getAccessor *GetAccessorDeclaration + if accessor.Kind == KindSetAccessor { + setAccessor = accessor.AsSetAccessorDeclaration() + if otherAccessor != nil { + getAccessor = otherAccessor.AsGetAccessorDeclaration() + } + } else { + getAccessor = accessor.AsGetAccessorDeclaration() + if otherAccessor != nil { + setAccessor = otherAccessor.AsSetAccessorDeclaration() + } + } + + return AllAccessorDeclarations{ + FirstAccessor: firstAccessor, + SecondAccessor: secondAccessor, + SetAccessor: setAccessor, + GetAccessor: getAccessor, + } +} + +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 + } +} diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 5db40b6f9f..b3e0705e6e 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -1970,7 +1970,7 @@ func isSameScopeDescendentOf(initial *ast.Node, parent *ast.Node, stopAt *ast.No if n == parent { return true } - if n == stopAt || ast.IsFunctionLike(n) && (ast.GetImmediatelyInvokedFunctionExpression(n) == nil || (getFunctionFlags(n)&FunctionFlagsAsyncGenerator != 0)) { + if n == stopAt || ast.IsFunctionLike(n) && (ast.GetImmediatelyInvokedFunctionExpression(n) == nil || (ast.GetFunctionFlags(n)&ast.FunctionFlagsAsyncGenerator != 0)) { return false } } @@ -2556,15 +2556,15 @@ func (c *Checker) checkSignatureDeclaration(node *ast.Node) { } } if returnTypeNode != nil { - functionFlags := getFunctionFlags(node) - if (functionFlags & (FunctionFlagsInvalid | FunctionFlagsGenerator)) == FunctionFlagsGenerator { + functionFlags := ast.GetFunctionFlags(node) + if (functionFlags & (ast.FunctionFlagsInvalid | ast.FunctionFlagsGenerator)) == ast.FunctionFlagsGenerator { returnType := c.getTypeFromTypeNode(returnTypeNode) if returnType == c.voidType { c.error(returnTypeNode, diagnostics.A_generator_cannot_have_a_void_type_annotation) } else { c.checkGeneratorInstantiationAssignabilityToReturnType(returnType, functionFlags, returnTypeNode) } - } else if (functionFlags & FunctionFlagsAsyncGenerator) == FunctionFlagsAsync { + } else if (functionFlags & ast.FunctionFlagsAsyncGenerator) == ast.FunctionFlagsAsync { c.checkAsyncFunctionReturnType(node, returnTypeNode) } } @@ -3209,7 +3209,7 @@ func (c *Checker) checkFunctionDeclaration(node *ast.Node) { func (c *Checker) checkFunctionOrMethodDeclaration(node *ast.Node) { c.checkDecorators(node) c.checkSignatureDeclaration(node) - functionFlags := getFunctionFlags(node) + functionFlags := ast.GetFunctionFlags(node) // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. @@ -3256,7 +3256,7 @@ func (c *Checker) checkFunctionOrMethodDeclaration(node *ast.Node) { if ast.NodeIsMissing(body) && !isPrivateWithinAmbient(node) { c.reportImplicitAny(node, c.anyType, WideningKindNormal) } - if functionFlags&FunctionFlagsGenerator != 0 && ast.NodeIsPresent(body) { + if functionFlags&ast.FunctionFlagsGenerator != 0 && ast.NodeIsPresent(body) { // A generator with a body and no type annotation can still cause errors. It can error if the // yielded values have no common supertype, or it can give an implicit any error if it has no // yielded values. The only way to trigger these errors is to try checking its return type. @@ -3525,7 +3525,7 @@ func (c *Checker) isImplementationCompatibleWithOverload(implementation *Signatu } func (c *Checker) checkAllCodePathsInNonVoidFunctionReturnOrThrow(fn *ast.Node, returnType *Type) { - functionFlags := getFunctionFlags(fn) + functionFlags := ast.GetFunctionFlags(fn) var t *Type if returnType != nil { t = c.unwrapReturnType(returnType, functionFlags) @@ -3577,7 +3577,7 @@ func (c *Checker) checkAllCodePathsInNonVoidFunctionReturnOrThrow(fn *ast.Node, } func (c *Checker) isUnwrappedReturnTypeUndefinedVoidOrAny(fn *ast.Node, returnType *Type) bool { - t := c.unwrapReturnType(returnType, getFunctionFlags(fn)) + t := c.unwrapReturnType(returnType, ast.GetFunctionFlags(fn)) return t != nil && (c.maybeTypeOfKind(t, TypeFlagsVoid) || t.flags&(TypeFlagsAny|TypeFlagsUndefined) != 0) } @@ -3891,7 +3891,7 @@ func (c *Checker) checkReturnStatement(node *ast.Node) { } signature := c.getSignatureFromDeclaration(container) returnType := c.getReturnTypeOfSignature(signature) - functionFlags := getFunctionFlags(container) + functionFlags := ast.GetFunctionFlags(container) exprNode := node.Expression() if c.strictNullChecks || exprNode != nil || returnType.flags&TypeFlagsNever != 0 { exprType := c.undefinedType @@ -3920,7 +3920,7 @@ func (c *Checker) checkReturnStatement(node *ast.Node) { // Otherwise, `node` is a return statement. func (c *Checker) checkReturnExpression(container *ast.Node, unwrappedReturnType *Type, node *ast.Node, expr *ast.Node, exprType *Type, inConditionalExpression bool) { unwrappedExprType := exprType - functionFlags := getFunctionFlags(container) + functionFlags := ast.GetFunctionFlags(container) if expr != nil { unwrappedExpr := ast.SkipParentheses(expr) if ast.IsConditionalExpression(unwrappedExpr) { @@ -3932,7 +3932,7 @@ func (c *Checker) checkReturnExpression(container *ast.Node, unwrappedReturnType } } inReturnStatement := node.Kind == ast.KindReturnStatement - if functionFlags&FunctionFlagsAsync != 0 { + if functionFlags&ast.FunctionFlagsAsync != 0 { unwrappedExprType = c.checkAwaitedType(exprType, false /*withAlias*/, node, diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) } effectiveExpr := expr // The effective expression for diagnostics purposes. @@ -9838,7 +9838,7 @@ func (c *Checker) contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node } func (c *Checker) checkFunctionExpressionOrObjectLiteralMethodDeferred(node *ast.Node) { - functionFlags := getFunctionFlags(node) + functionFlags := ast.GetFunctionFlags(node) returnType := c.getReturnTypeFromAnnotation(node) c.checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType) body := node.Body() @@ -10467,12 +10467,12 @@ func (c *Checker) checkYieldExpression(node *ast.Node) *Type { if fn == nil { return c.anyType } - functionFlags := getFunctionFlags(fn) - if functionFlags&FunctionFlagsGenerator == 0 { + functionFlags := ast.GetFunctionFlags(fn) + if functionFlags&ast.FunctionFlagsGenerator == 0 { // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. return c.anyType } - isAsync := (functionFlags & FunctionFlagsAsync) != 0 + isAsync := (functionFlags & ast.FunctionFlagsAsync) != 0 // There is no point in doing an assignability check if the function // has no explicit return type because the return type is directly computed // from the yield expressions. @@ -19248,9 +19248,9 @@ func (c *Checker) getReturnTypeFromBody(fn *ast.Node, checkMode CheckMode) *Type if body == nil { return c.errorType } - functionFlags := getFunctionFlags(fn) - isAsync := (functionFlags & FunctionFlagsAsync) != 0 - isGenerator := (functionFlags & FunctionFlagsGenerator) != 0 + functionFlags := ast.GetFunctionFlags(fn) + isAsync := (functionFlags & ast.FunctionFlagsAsync) != 0 + isGenerator := (functionFlags & ast.FunctionFlagsGenerator) != 0 var returnType *Type var yieldType *Type var nextType *Type @@ -19283,7 +19283,7 @@ func (c *Checker) getReturnTypeFromBody(fn *ast.Node, checkMode CheckMode) *Type types, isNeverReturning := c.checkAndAggregateReturnExpressionTypes(fn, checkMode) if isNeverReturning { // For an async function, the return type will not be never, but rather a Promise for never. - if functionFlags&FunctionFlagsAsync != 0 { + if functionFlags&ast.FunctionFlagsAsync != 0 { return c.createPromiseReturnType(fn, c.neverType) } // Normal function @@ -19298,7 +19298,7 @@ func (c *Checker) getReturnTypeFromBody(fn *ast.Node, checkMode CheckMode) *Type } else { returnType = c.voidType } - if functionFlags&FunctionFlagsAsync != 0 { + if functionFlags&ast.FunctionFlagsAsync != 0 { return c.createPromiseReturnType(fn, returnType) } // Normal function @@ -19374,7 +19374,7 @@ func (c *Checker) getReturnTypeFromBody(fn *ast.Node, checkMode CheckMode) *Type // Returns the aggregated list of return types, plus a bool indicating a never-returning function. func (c *Checker) checkAndAggregateReturnExpressionTypes(fn *ast.Node, checkMode CheckMode) ([]*Type, bool) { - functionFlags := getFunctionFlags(fn) + functionFlags := ast.GetFunctionFlags(fn) var aggregatedTypes []*Type hasReturnWithNoExpression := c.functionHasImplicitReturn(fn) hasReturnOfTypeNever := false @@ -19387,7 +19387,7 @@ func (c *Checker) checkAndAggregateReturnExpressionTypes(fn *ast.Node, checkMode expr = ast.SkipParentheses(expr) // Bare calls to this same function don't contribute to inference // and `return await` is also safe to unwrap here - if functionFlags&FunctionFlagsAsync != 0 && ast.IsAwaitExpression(expr) { + if functionFlags&ast.FunctionFlagsAsync != 0 && ast.IsAwaitExpression(expr) { expr = ast.SkipParentheses(expr.Expression()) } if ast.IsCallExpression(expr) && ast.IsIdentifier(expr.Expression()) && c.checkExpressionCached(expr.Expression()).symbol == c.getMergedSymbol(fn.Symbol()) && @@ -19396,7 +19396,7 @@ func (c *Checker) checkAndAggregateReturnExpressionTypes(fn *ast.Node, checkMode return false } t := c.checkExpressionCachedEx(expr, checkMode & ^CheckModeSkipGenericFunctions) - if functionFlags&FunctionFlagsAsync != 0 { + if functionFlags&ast.FunctionFlagsAsync != 0 { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the // return type of the body should be unwrapped to its awaited type, which should be wrapped in @@ -19434,7 +19434,7 @@ func mayReturnNever(fn *ast.Node) bool { } func (c *Checker) checkAndAggregateYieldOperandTypes(fn *ast.Node, checkMode CheckMode) (yieldTypes []*Type, nextTypes []*Type) { - isAsync := (getFunctionFlags(fn) & FunctionFlagsAsync) != 0 + isAsync := (ast.GetFunctionFlags(fn) & ast.FunctionFlagsAsync) != 0 forEachYieldExpression(fn.Body(), func(yieldExpr *ast.Node) { yieldExprType := c.undefinedWideningType if yieldExpr.Expression() != nil { @@ -19495,9 +19495,9 @@ func (c *Checker) createPromiseReturnType(fn *ast.Node, promisedType *Type) *Typ return promiseType } -func (c *Checker) unwrapReturnType(returnType *Type, functionFlags FunctionFlags) *Type { - isGenerator := functionFlags&FunctionFlagsGenerator != 0 - isAsync := functionFlags&FunctionFlagsAsync != 0 +func (c *Checker) unwrapReturnType(returnType *Type, functionFlags ast.FunctionFlags) *Type { + isGenerator := functionFlags&ast.FunctionFlagsGenerator != 0 + isAsync := functionFlags&ast.FunctionFlagsAsync != 0 if isGenerator { returnIterationType := c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, returnType, isAsync) if returnIterationType == nil { @@ -19576,20 +19576,20 @@ func (c *Checker) shouldReportErrorsFromWideningWithContextualSignature(declarat return true } returnType := c.getReturnTypeOfSignature(signature) - flags := getFunctionFlags(declaration) + flags := ast.GetFunctionFlags(declaration) switch wideningKind { case WideningKindFunctionReturn: - if flags&FunctionFlagsGenerator != 0 { - returnType = core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, returnType, flags&FunctionFlagsAsync != 0), returnType) - } else if flags&FunctionFlagsAsync != 0 { + if flags&ast.FunctionFlagsGenerator != 0 { + returnType = core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, returnType, flags&ast.FunctionFlagsAsync != 0), returnType) + } else if flags&ast.FunctionFlagsAsync != 0 { returnType = core.OrElse(c.getAwaitedTypeNoAlias(returnType), returnType) } return c.isGenericType(returnType) case WideningKindGeneratorYield: - yieldType := c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindYield, returnType, flags&FunctionFlagsAsync != 0) + yieldType := c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindYield, returnType, flags&ast.FunctionFlagsAsync != 0) return yieldType != nil && c.isGenericType(yieldType) case WideningKindGeneratorNext: - nextType := c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindNext, returnType, flags&FunctionFlagsAsync != 0) + nextType := c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindNext, returnType, flags&ast.FunctionFlagsAsync != 0) return nextType != nil && c.isGenericType(nextType) } return false @@ -19647,8 +19647,8 @@ func (c *Checker) getTypePredicateFromBody(fn *ast.Node) *TypePredicate { case ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: return nil } - functionFlags := getFunctionFlags(fn) - if functionFlags != FunctionFlagsNormal { + functionFlags := ast.GetFunctionFlags(fn) + if functionFlags != ast.FunctionFlagsNormal { return nil } // Only attempt to infer a type predicate if there's exactly one return. @@ -20990,54 +20990,8 @@ func isConflictingPrivateProperty(prop *ast.Symbol) bool { return prop.ValueDeclaration == nil && prop.CheckFlags&ast.CheckFlagsContainsPrivate != 0 } -type allAccessorDeclarations struct { - firstAccessor *ast.AccessorDeclaration - secondAccessor *ast.AccessorDeclaration - setAccessor *ast.SetAccessorDeclaration - getAccessor *ast.GetAccessorDeclaration -} - -func (c *Checker) getAllAccessorDeclarationsForDeclaration(accessor *ast.AccessorDeclaration) allAccessorDeclarations { - var otherKind ast.Kind - if accessor.Kind == ast.KindSetAccessor { - otherKind = ast.KindGetAccessor - } else if accessor.Kind == ast.KindGetAccessor { - otherKind = ast.KindSetAccessor - } else { - panic(fmt.Sprintf("Unexpected node kind %q", accessor.Kind)) - } - otherAccessor := ast.GetDeclarationOfKind(c.getSymbolOfDeclaration(accessor), otherKind) - - var firstAccessor *ast.AccessorDeclaration - var secondAccessor *ast.AccessorDeclaration - if otherAccessor != nil && (otherAccessor.Pos() < accessor.Pos()) { - firstAccessor = otherAccessor - secondAccessor = accessor - } else { - firstAccessor = accessor - secondAccessor = otherAccessor - } - - var setAccessor *ast.SetAccessorDeclaration - var getAccessor *ast.GetAccessorDeclaration - if accessor.Kind == ast.KindSetAccessor { - setAccessor = accessor.AsSetAccessorDeclaration() - if otherAccessor != nil { - getAccessor = otherAccessor.AsGetAccessorDeclaration() - } - } else { - getAccessor = accessor.AsGetAccessorDeclaration() - if otherAccessor != nil { - setAccessor = otherAccessor.AsSetAccessorDeclaration() - } - } - - return allAccessorDeclarations{ - firstAccessor: firstAccessor, - secondAccessor: secondAccessor, - setAccessor: setAccessor, - getAccessor: getAccessor, - } +func (c *Checker) getAllAccessorDeclarationsForDeclaration(accessor *ast.AccessorDeclaration) ast.AllAccessorDeclarations { + return ast.GetAllAccessorDeclarationsForDeclaration(accessor, c.getSymbolOfDeclaration(accessor)) } func (c *Checker) getTypeArguments(t *Type) []*Type { @@ -28233,22 +28187,22 @@ func (c *Checker) getContextualTypeForReturnExpression(node *ast.Node, contextFl if fn != nil { contextualReturnType := c.getContextualReturnType(fn, contextFlags) if contextualReturnType != nil { - functionFlags := getFunctionFlags(fn) - if functionFlags&FunctionFlagsGenerator != 0 { - isAsyncGenerator := (functionFlags & FunctionFlagsAsync) != 0 + functionFlags := ast.GetFunctionFlags(fn) + if functionFlags&ast.FunctionFlagsGenerator != 0 { + isAsyncGenerator := (functionFlags & ast.FunctionFlagsAsync) != 0 if contextualReturnType.flags&TypeFlagsUnion != 0 { contextualReturnType = c.filterType(contextualReturnType, func(t *Type) bool { return c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, t, isAsyncGenerator) != nil }) } - iterationReturnType := c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, contextualReturnType, (functionFlags&FunctionFlagsAsync) != 0) + iterationReturnType := c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, contextualReturnType, (functionFlags&ast.FunctionFlagsAsync) != 0) if iterationReturnType == nil { return nil } contextualReturnType = iterationReturnType // falls through to unwrap Promise for AsyncGenerators } - if functionFlags&FunctionFlagsAsync != 0 { + if functionFlags&ast.FunctionFlagsAsync != 0 { // Get the awaited type without the `Awaited` alias contextualAwaitedType := c.mapType(contextualReturnType, c.getAwaitedTypeNoAlias) return c.getUnionType([]*Type{contextualAwaitedType, c.createPromiseLikeType(contextualAwaitedType)}) @@ -28261,7 +28215,7 @@ func (c *Checker) getContextualTypeForReturnExpression(node *ast.Node, contextFl } func (c *Checker) getContextualIterationType(kind IterationTypeKind, functionDecl *ast.Node) *Type { - isAsync := getFunctionFlags(functionDecl)&FunctionFlagsAsync != 0 + isAsync := ast.GetFunctionFlags(functionDecl)&ast.FunctionFlagsAsync != 0 contextualReturnType := c.getContextualReturnType(functionDecl, ContextFlagsNone) if contextualReturnType != nil { return c.getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) @@ -28281,13 +28235,13 @@ func (c *Checker) getContextualReturnType(functionDecl *ast.Node, contextFlags C signature := c.getContextualSignatureForFunctionLikeDeclaration(functionDecl) if signature != nil && !c.isResolvingReturnTypeOfSignature(signature) { returnType := c.getReturnTypeOfSignature(signature) - functionFlags := getFunctionFlags(functionDecl) - if functionFlags&FunctionFlagsGenerator != 0 { + functionFlags := ast.GetFunctionFlags(functionDecl) + if functionFlags&ast.FunctionFlagsGenerator != 0 { return c.filterType(returnType, func(t *Type) bool { return t.flags&(TypeFlagsAnyOrUnknown|TypeFlagsVoid|TypeFlagsInstantiableNonPrimitive) != 0 || c.checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, nil /*errorNode*/) }) } - if functionFlags&FunctionFlagsAsync != 0 { + if functionFlags&ast.FunctionFlagsAsync != 0 { return c.filterType(returnType, func(t *Type) bool { return t.flags&(TypeFlagsAnyOrUnknown|TypeFlagsVoid|TypeFlagsInstantiableNonPrimitive) != 0 || c.getAwaitedTypeOfPromise(t) != nil }) @@ -28301,17 +28255,17 @@ func (c *Checker) getContextualReturnType(functionDecl *ast.Node, contextFlags C return nil } -func (c *Checker) checkGeneratorInstantiationAssignabilityToReturnType(returnType *Type, functionFlags FunctionFlags, errorNode *ast.Node) bool { +func (c *Checker) checkGeneratorInstantiationAssignabilityToReturnType(returnType *Type, functionFlags ast.FunctionFlags, errorNode *ast.Node) bool { // Naively, one could check that Generator is assignable to the return type annotation. // However, that would not catch the error in the following case. // // interface BadGenerator extends Iterable, Iterator { } // function* g(): BadGenerator { } // Iterable and Iterator have different types! // - generatorYieldType := core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindYield, returnType, (functionFlags&FunctionFlagsAsync) != 0), c.anyType) - generatorReturnType := core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, returnType, (functionFlags&FunctionFlagsAsync) != 0), generatorYieldType) - generatorNextType := core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindNext, returnType, (functionFlags&FunctionFlagsAsync) != 0), c.unknownType) - generatorInstantiation := c.createGeneratorType(generatorYieldType, generatorReturnType, generatorNextType, functionFlags&FunctionFlagsAsync != 0) + generatorYieldType := core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindYield, returnType, (functionFlags&ast.FunctionFlagsAsync) != 0), c.anyType) + generatorReturnType := core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindReturn, returnType, (functionFlags&ast.FunctionFlagsAsync) != 0), generatorYieldType) + generatorNextType := core.OrElse(c.getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKindNext, returnType, (functionFlags&ast.FunctionFlagsAsync) != 0), c.unknownType) + generatorInstantiation := c.createGeneratorType(generatorYieldType, generatorReturnType, generatorNextType, functionFlags&ast.FunctionFlagsAsync != 0) return c.checkTypeAssignableTo(generatorInstantiation, returnType, errorNode, nil) } @@ -28326,10 +28280,10 @@ func (c *Checker) getContextualSignatureForFunctionLikeDeclaration(node *ast.Nod func (c *Checker) getContextualTypeForYieldOperand(node *ast.Node, contextFlags ContextFlags) *Type { fn := getContainingFunction(node) if fn != nil { - functionFlags := getFunctionFlags(fn) + functionFlags := ast.GetFunctionFlags(fn) contextualReturnType := c.getContextualReturnType(fn, contextFlags) if contextualReturnType != nil { - isAsyncGenerator := functionFlags&FunctionFlagsAsync != 0 + isAsyncGenerator := functionFlags&ast.FunctionFlagsAsync != 0 isYieldStar := node.AsYieldExpression().AsteriskToken != nil if !isYieldStar && contextualReturnType.flags&TypeFlagsUnion != 0 { contextualReturnType = c.filterType(contextualReturnType, func(t *Type) bool { diff --git a/internal/checker/grammarchecks.go b/internal/checker/grammarchecks.go index dc98b20881..c0129f6690 100644 --- a/internal/checker/grammarchecks.go +++ b/internal/checker/grammarchecks.go @@ -246,7 +246,7 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has } } else if c.legacyDecorators && (node.Kind == ast.KindGetAccessor || node.Kind == ast.KindSetAccessor) { accessors := c.getAllAccessorDeclarationsForDeclaration(node) - if ast.HasDecorators(accessors.firstAccessor) && node == accessors.secondAccessor { + if ast.HasDecorators(accessors.FirstAccessor) && node == accessors.SecondAccessor { return c.grammarErrorOnFirstToken(node, diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name) } } @@ -1265,7 +1265,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 := 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.") } diff --git a/internal/checker/nodebuilderimpl.go b/internal/checker/nodebuilderimpl.go index 9f07545c05..832de4e14d 100644 --- a/internal/checker/nodebuilderimpl.go +++ b/internal/checker/nodebuilderimpl.go @@ -2435,7 +2435,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); + // type ErrAlias = typeof Err; + // declare const e: ErrAlias; + // ErrAlias = typeof Err = typeof ErrImpl & (() => number) + // The problem is each constituent of the intersection will be associated with typeof Err + // And when extracting a type for typeof ErrImpl from typeof Err 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 diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 2219197356..949d1a4761 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -657,7 +657,7 @@ func (c *Checker) elaborateArrowFunction(node *ast.Node, source *Type, target *T if target.symbol != nil && len(target.symbol.Declarations) != 0 { diagnostic.AddRelatedInfo(createDiagnosticForNode(target.symbol.Declarations[0], diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature)) } - if getFunctionFlags(node)&FunctionFlagsAsync == 0 && c.getTypeOfPropertyOfType(sourceReturn, "then") == nil && c.checkTypeRelatedTo(c.createPromiseType(sourceReturn), targetReturn, relation, nil /*errorNode*/) { + if ast.GetFunctionFlags(node)&ast.FunctionFlagsAsync == 0 && c.getTypeOfPropertyOfType(sourceReturn, "then") == nil && c.checkTypeRelatedTo(c.createPromiseType(sourceReturn), targetReturn, relation, nil /*errorNode*/) { diagnostic.AddRelatedInfo(createDiagnosticForNode(node, diagnostics.Did_you_mean_to_mark_this_function_as_async)) } c.reportDiagnostic(diagnostic, diagnosticOutput) diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 10f1c6c0c7..0869516674 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -1280,42 +1280,6 @@ func getMembersOfDeclaration(node *ast.Node) []*ast.Node { return nil } -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 *ast.Node) FunctionFlags { - if node == nil { - return FunctionFlagsInvalid - } - data := node.BodyData() - if data == nil { - return FunctionFlagsInvalid - } - flags := FunctionFlagsNormal - switch node.Kind { - case ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindMethodDeclaration: - if data.AsteriskToken != nil { - flags |= FunctionFlagsGenerator - } - fallthrough - case ast.KindArrowFunction: - if ast.HasSyntacticModifier(node, ast.ModifierFlagsAsync) { - flags |= FunctionFlagsAsync - } - } - if data.Body == nil { - flags |= FunctionFlagsInvalid - } - return flags -} - func isInRightSideOfImportOrExportAssignment(node *ast.EntityName) bool { for node.Parent.Kind == ast.KindQualifiedName { node = node.Parent diff --git a/internal/psuedochecker/checker.go b/internal/psuedochecker/checker.go new file mode 100644 index 0000000000..46719c4f3f --- /dev/null +++ b/internal/psuedochecker/checker.go @@ -0,0 +1,18 @@ +// psuedochecker is a limited "checker" that returns psuedo-"types" of expressions - mostly those which trivially have type nodes +package psuedochecker + +// 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 +// just use Node.Symbol, which will fail to pair up late-bound symbols. In theory, this is actually +// fine, since ID can't possibly know if `set [q1()](a){}` and `get [q2()](): T {}` are connected +// without performing real type checking, regardless, so it shouldn't matter. If anything, it might be +// OK to add a "dumb" late binder that can merge multiple `[a.b.c]: T` together, but not anything else. +// This is an area of active ~~feature-creep~~ development in ID output, prerequisite refactoring would include +// extracting the `mergeSymbol` core checker logic into a reusable component. + +type PsuedoChecker struct{} + +func NewPsuedoChecker() *PsuedoChecker { + return &PsuedoChecker{} +} diff --git a/internal/psuedochecker/lookup.go b/internal/psuedochecker/lookup.go new file mode 100644 index 0000000000..b9d757d967 --- /dev/null +++ b/internal/psuedochecker/lookup.go @@ -0,0 +1,520 @@ +package psuedochecker + +import ( + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/debug" +) + +func (ch *PsuedoChecker) GetReturnTypeOfSignature(signatureNode *ast.Node) *PsuedoType { + switch signatureNode.Kind { + case ast.KindGetAccessor: + return ch.GetTypeOfAccessor(signatureNode.AsGetAccessorDeclaration()) + case ast.KindMethodDeclaration, ast.KindFunctionDeclaration, ast.KindConstructor, + ast.KindMethodSignature, ast.KindCallSignature, ast.KindConstructSignature, + ast.KindSetAccessor, ast.KindIndexSignature, ast.KindFunctionType, ast.KindConstructorType, + ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindJSDocSignature: + return ch.createReturnFromSignature(signatureNode) + default: + debug.FailBadSyntaxKind(signatureNode, "Node needs to be an inferrable node") + return nil + } +} + +func (ch *PsuedoChecker) GetTypeOfAccessor(accessor *ast.GetAccessorDeclaration) *PsuedoType { + annotated := ch.typeFromAccessor(accessor) + if annotated.Kind == PsuedoTypeKindNoResult { + return ch.inferAccessorType(accessor.AsNode()) + } + return annotated +} + +func (ch *PsuedoChecker) GetTypeOfExpression(node *ast.Node) *PsuedoType { + return ch.typeFromExpression(node) +} + +func (ch *PsuedoChecker) GetTypeOfDeclaration(node *ast.Node) *PsuedoType { + switch node.Kind { + case ast.KindParameter: + return ch.typeFromParameter(node.AsParameterDeclaration()) + case ast.KindVariableDeclaration: + return ch.typeFromVariable(node.AsVariableDeclaration()) + case ast.KindPropertySignature, ast.KindPropertyDeclaration, ast.KindJSDocPropertyTag: + return ch.typeFromProperty(node) + case ast.KindBindingElement: + return NewPsuedoTypeNoResult(node) + case ast.KindExportAssignment: + return ch.typeFromExpression(node.AsExportAssignment().Expression) + case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression, ast.KindBinaryExpression: + return ch.typeFromExpandoProperty(node) + case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment: + return ch.typeFromPropertyAssignment(node) + default: + debug.FailBadSyntaxKind(node, "node needs to be an inferrable node") + return nil + } +} + +func (ch *PsuedoChecker) typeFromPropertyAssignment(node *ast.Node) *PsuedoType { + annotation := node.Type() + if annotation != nil { + return NewPsuedoTypeDirect(annotation) + } + if node.Kind == ast.KindPropertyAssignment { + init := node.Initializer() + if init != nil { + return ch.typeFromExpression(init) + } + } + return NewPsuedoTypeNoResult(node) +} + +// TODO: Is this redundant with the reparser in place? +func (ch *PsuedoChecker) typeFromExpandoProperty(node *ast.Node) *PsuedoType { + declaredType := node.Type() + if declaredType != nil { + return NewPsuedoTypeDirect(declaredType) + } + return NewPsuedoTypeInferred(node) +} + +func (ch *PsuedoChecker) typeFromProperty(node *ast.Node) *PsuedoType { + t := node.Type() + if t != nil { + return NewPsuedoTypeDirect(t) + } + if ast.IsPropertyDeclaration(node) { + init := node.Initializer() + if init != nil && !isContextuallyTyped(node) { + return ch.typeFromExpression(init) + } + } + return NewPsuedoTypeNoResult(node) +} + +func (ch *PsuedoChecker) typeFromVariable(declaration *ast.VariableDeclaration) *PsuedoType { + t := declaration.Type + if t != nil { + return NewPsuedoTypeDirect(t) + } + init := declaration.Initializer + if init != nil && (len(declaration.Symbol.Declarations) == 1 || core.CountWhere(declaration.Symbol.Declarations, ast.IsVariableDeclaration) == 1) { + if !isContextuallyTyped(declaration.AsNode()) { // TODO: also should bail on expando declarations; reuse syntactic expando check used in declaration emit + return ch.typeFromExpression(init) + } + } + return NewPsuedoTypeNoResult(declaration.AsNode()) +} + +func (ch *PsuedoChecker) typeFromAccessor(accessor *ast.GetAccessorDeclaration) *PsuedoType { + accessorDeclarations := ast.GetAllAccessorDeclarationsForDeclaration(accessor.AsNode(), accessor.Symbol) + accessorType := ch.getTypeAnnotationFromAllAccessorDeclarations(accessor.AsNode(), accessorDeclarations) + if accessorType != nil && !ast.IsTypePredicateNode(accessorType) { + return NewPsuedoTypeDirect(accessorType) + } + if accessorDeclarations.GetAccessor != nil { + return ch.createReturnFromSignature(accessorDeclarations.GetAccessor.AsNode()) + } + return NewPsuedoTypeNoResult(accessor.AsNode()) +} + +func (ch *PsuedoChecker) inferAccessorType(node *ast.Node) *PsuedoType { + if node.Kind == ast.KindGetAccessor { + return ch.createReturnFromSignature(node) + } + return NewPsuedoTypeNoResult(node) +} + +func (ch *PsuedoChecker) getTypeAnnotationFromAllAccessorDeclarations(node *ast.Node, accessors ast.AllAccessorDeclarations) *ast.Node { + accessorType := ch.getTypeAnnotationFromAccessor(node) + if accessorType == nil && node != accessors.FirstAccessor { + accessorType = ch.getTypeAnnotationFromAccessor(accessors.FirstAccessor) + } + if accessorType == nil && accessors.SecondAccessor != nil && node != accessors.SecondAccessor { + accessorType = ch.getTypeAnnotationFromAccessor(accessors.SecondAccessor) + } + return accessorType +} + +func (ch *PsuedoChecker) getTypeAnnotationFromAccessor(node *ast.Node) *ast.Node { + if node == nil { + return nil + } + // !!! TODO: support ripping return type off of .FullSignature + if node.Kind == ast.KindGetAccessor { + return node.AsGetAccessorDeclaration().Type + } + set := node.AsSetAccessorDeclaration() + if set.Parameters == nil || len(set.Parameters.Nodes) < 1 { + return nil + } + p := set.Parameters.Nodes[0] + if !ast.IsParameter(p) { + return nil + } + return p.AsParameterDeclaration().Type +} + +func isValueSignatureDeclaration(node *ast.Node) bool { + return ast.IsFunctionExpression(node) || ast.IsArrowFunction(node) || ast.IsMethodDeclaration(node) || ast.IsAccessor(node) || ast.IsFunctionDeclaration(node) || ast.IsConstructorDeclaration(node) +} + +// does not return `nil`, returns a `NoResult` psuedotype instead +func (ch *PsuedoChecker) createReturnFromSignature(fn *ast.Node) *PsuedoType { + if ast.IsFunctionLike(fn) { + d := fn.FunctionLikeData() + // !!! TODO: support ripping return type off of .FullSignature + r := d.Type + if r != nil { + return NewPsuedoTypeDirect(r) + } + } + if isValueSignatureDeclaration(fn) { + return ch.typeFromSingleReturnExpression(fn) + } + return NewPsuedoTypeNoResult(fn) +} + +func (ch *PsuedoChecker) typeFromSingleReturnExpression(fn *ast.Node) *PsuedoType { + var candidateExpr *ast.Node + if fn != nil && !ast.NodeIsMissing(fn.Body()) { + flags := ast.GetFunctionFlags(fn) + if flags&ast.FunctionFlagsAsyncGenerator != 0 { + return NewPsuedoTypeNoResult(fn) + } + + body := fn.Body() + if ast.IsBlock(body) { + ast.ForEachReturnStatement(body, func(stmt *ast.Node) bool { + if stmt.Parent != body { // Why bail on nested return statements? + candidateExpr = nil + return true + } + if candidateExpr == nil { + candidateExpr = stmt.AsReturnStatement().Expression + } else { + candidateExpr = nil + return true + } + return false + }) + } else { + candidateExpr = body + } + } + if candidateExpr != nil { + if isContextuallyTyped(candidateExpr) { + var t *ast.Node + if candidateExpr.Kind == ast.KindTypeAssertionExpression { + t = candidateExpr.AsTypeAssertion().Type + } else if candidateExpr.Kind == ast.KindAsExpression { + t = candidateExpr.AsAsExpression().Type + } + if t != nil && !ast.IsConstTypeReference(t) { + return NewPsuedoTypeDirect(t) + } + } else { + return ch.typeFromExpression(candidateExpr) + } + } + return NewPsuedoTypeNoResult(fn) +} + +// This is basically `checkExpression` for psuedotypes +func (ch *PsuedoChecker) typeFromExpression(node *ast.Node) *PsuedoType { + switch node.Kind { + case ast.KindOmittedExpression: + return PsuedoTypeUndefined + case ast.KindParenthesizedExpression: + // assertions transformed on reparse, just unwrap + return ch.typeFromExpression(node.AsParenthesizedExpression().Expression) + case ast.KindIdentifier: + // !!! TODO: in strada, this uses symbol information to ensure `node` refers to the global `undefined` symbol instead + // we should probably import `resolveName` and use it here to check for the same; but we have to setup some barebones psuedoglobals for that to work! + if node.AsIdentifier().Text == "undefined" { + return PsuedoTypeUndefined + } + case ast.KindNullKeyword: + return PsuedoTypeNull + case ast.KindArrowFunction, ast.KindFunctionExpression: + return ch.typeFromFunctionLikeExpression(node) + case ast.KindTypeAssertionExpression: + return ch.typeFromTypeAssertion(node.AsTypeAssertion().Expression, node.AsTypeAssertion().Type) + case ast.KindAsExpression: + return ch.typeFromTypeAssertion(node.AsAsExpression().Expression, node.AsAsExpression().Type) + case ast.KindPrefixUnaryExpression: + if ast.IsPrimitiveLiteralValue(node, true) { + return ch.typeFromPrimitiveLiteralPrefix(node.AsPrefixUnaryExpression()) + } + case ast.KindArrayLiteralExpression: + return ch.typeFromArrayLiteral(node.AsArrayLiteralExpression()) + case ast.KindObjectLiteralExpression: + return ch.typeFromObjectLiteral(node.AsObjectLiteralExpression()) + case ast.KindClassExpression: + return NewPsuedoTypeInferred(node) // No possible annotation/directly mappable syntax + case ast.KindTemplateExpression: + if ch.isInConstContext(node) { + return NewPsuedoTypeInferred(node) // templateLitWithHoles as const, not supported + } + return PsuedoTypeString + case ast.KindNumericLiteral: + if ch.isInConstContext(node) { + return NewPsuedoTypeNumericLiteral(node) + } + return PsuedoTypeNumber + case ast.KindNoSubstitutionTemplateLiteral: + if ch.isInConstContext(node) { + return NewPsuedoTypeStringLiteral(node) + } + return PsuedoTypeString + case ast.KindStringLiteral: + if ch.isInConstContext(node) { + return NewPsuedoTypeStringLiteral(node) + } + return PsuedoTypeString + case ast.KindBigIntLiteral: + if ch.isInConstContext(node) { + return NewPsuedoTypeBigIntLiteral(node) + } + return PsuedoTypeBigInt + case ast.KindTrueKeyword: + if ch.isInConstContext(node) { + return PsuedoTypeTrue + } + return PsuedoTypeBoolean + case ast.KindFalseKeyword: + if ch.isInConstContext(node) { + return PsuedoTypeFalse + } + return PsuedoTypeBoolean + } + return NewPsuedoTypeInferred(node) +} + +func (ch *PsuedoChecker) typeFromObjectLiteral(node *ast.ObjectLiteralExpression) *PsuedoType { + if !ch.canGetTypeFromObjectLiteral(node) { + return NewPsuedoTypeInferred(node.AsNode()) + } + // we are in a const context producing an object literal type, there are no shorthand or spread assignments + if node.Properties == nil || len(node.Properties.Nodes) == 0 { + return NewPsuedoTypeObjectLiteral(nil) + } + results := make([]*PsuedoObjectElement, 0, len(node.Properties.Nodes)) + for _, e := range node.Properties.Nodes { + switch e.Kind { + case ast.KindMethodDeclaration: + optional := e.AsMethodDeclaration().PostfixToken != nil && e.AsMethodDeclaration().PostfixToken.Kind == ast.KindQuestionToken + if e.FunctionLikeData().FullSignature != nil { + results = append(results, NewPsuedoPropertyAssignment( + e.Name(), + optional, + NewPsuedoTypeDirect(e.FunctionLikeData().FullSignature), + )) + } else { + results = append(results, NewPsuedoObjectMethod( + e.Name(), + optional, + ch.cloneParameters(e.ParameterList()), + ch.createReturnFromSignature(e), + )) + } + case ast.KindPropertyAssignment: + results = append(results, NewPsuedoPropertyAssignment( + e.Name(), + e.AsPropertyAssignment().PostfixToken != nil && e.AsPropertyAssignment().PostfixToken.Kind == ast.KindQuestionToken, + ch.typeFromExpression(e.Initializer()), + )) + case ast.KindSetAccessor: + results = append(results, NewPsuedoSetAccessor( + e.Name(), + false, + ch.cloneParameters(e.AsSetAccessorDeclaration().Parameters)[0], + )) + case ast.KindGetAccessor: + results = append(results, NewPsuedoGetAccessor( + e.Name(), + false, + ch.typeFromAccessor(e.AsGetAccessorDeclaration()), + )) + } + } + return NewPsuedoTypeObjectLiteral(results) +} + +func (ch *PsuedoChecker) canGetTypeFromObjectLiteral(node *ast.ObjectLiteralExpression) bool { + if node.Properties == nil || len(node.Properties.Nodes) == 0 { + return true // empty object + } + // !!! TODO: strada reports errors on multiple non-inferrable props + // via calling reportInferenceFallback multiple times here before returning. + // Does that logic need to be included in this checker? Or can it + // be kept to the `PsuedoType` -> `Node` mapping logic, so this + // checker can avoid needing any error reporting logic? + for _, e := range node.Properties.Nodes { + if e.Flags&ast.NodeFlagsThisNodeHasError != 0 { + return false + } + if e.Kind == ast.KindShorthandPropertyAssignment || e.Kind == ast.KindSpreadAssignment { + return false + } + if e.Name().Flags&ast.NodeFlagsThisNodeHasError != 0 { + return false + } + if e.Name().Kind == ast.KindPrivateIdentifier { + return false + } + if e.Name().Kind == ast.KindComputedPropertyName { + expression := e.Name().Expression() + if !ast.IsPrimitiveLiteralValue(expression, false) { + return false + } + } + } + return true +} + +func (ch *PsuedoChecker) typeFromArrayLiteral(node *ast.ArrayLiteralExpression) *PsuedoType { + if !ch.canGetTypeFromArrayLiteral(node) { + return NewPsuedoTypeInferred(node.AsNode()) + } + // we are in a const context producing a tuple type, there are no spread elements + results := make([]*PsuedoType, 0, len(node.Elements.Nodes)) + for _, e := range node.Elements.Nodes { + results = append(results, ch.typeFromExpression(e)) + } + return NewPsuedoTypeTuple(results) +} + +func (ch *PsuedoChecker) canGetTypeFromArrayLiteral(node *ast.ArrayLiteralExpression) bool { + if !ch.isInConstContext(node.AsNode()) { + return false + } + for _, e := range node.Elements.Nodes { + if e.Kind == ast.KindSpreadElement { + return false + } + } + return true +} + +// Traverses up the parent chain to determine if the node is within a const context without needing any +// persistent traversal scope tracking (which could be unreliable in the presence of `typeof` queries anyway!) +func (ch *PsuedoChecker) isInConstContext(node *ast.Node) bool { + // An expression is in a const context if an ancestor is a const type maybeAssertion expression + maybeAssertion := ast.FindAncestor( + node, + func(n *ast.Node) bool { + // stop traversing up at assertions, new scopes, and anything not an expression - they're contextual barriers + return ast.IsAssertionExpression(n) || ast.IsFunctionLike(n) || !ast.IsExpressionNode(n) + }, + ) + return ast.IsConstAssertion(maybeAssertion) +} + +func (ch *PsuedoChecker) typeFromPrimitiveLiteralPrefix(node *ast.PrefixUnaryExpression) *PsuedoType { + inner := node.Expression() + if inner.Kind == ast.KindBigIntLiteral { + if ch.isInConstContext(node.AsNode()) { + return NewPsuedoTypeBigIntLiteral(node.AsNode()) + } + return PsuedoTypeBigInt + } + if inner.Kind == ast.KindNumericLiteral { + if ch.isInConstContext(node.AsNode()) { + return NewPsuedoTypeNumericLiteral(node.AsNode()) + } + return PsuedoTypeNumber + } + debug.FailBadSyntaxKind(inner) + return nil +} + +func (ch *PsuedoChecker) typeFromTypeAssertion(expression *ast.Node, typeNode *ast.Node) *PsuedoType { + if ast.IsConstTypeReference(typeNode) { + return ch.typeFromExpression(expression) + } + return NewPsuedoTypeDirect(typeNode) +} + +func (ch *PsuedoChecker) typeFromFunctionLikeExpression(node *ast.Node) *PsuedoType { + if node.FunctionLikeData().FullSignature != nil { + return NewPsuedoTypeDirect(node.FunctionLikeData().FullSignature) + } + returnType := ch.createReturnFromSignature(node) + if returnType.Kind == PsuedoTypeKindNoResult { + // no result for the return type can just be an inferred result for the whole expression + return NewPsuedoTypeInferred(node.AsNode()) + } + typeParameters := ch.cloneTypeParameters(node.FunctionLikeData().TypeParameters) + parameters := ch.cloneParameters(node.FunctionLikeData().Parameters) + return NewPsuedoTypeSingleCallSignature( + parameters, + typeParameters, + returnType, + ) +} + +func (ch *PsuedoChecker) cloneTypeParameters(nodes *ast.NodeList) []*ast.TypeParameterDeclaration { + if nodes == nil { + return nil + } + if len(nodes.Nodes) == 0 { + return nil + } + result := make([]*ast.TypeParameterDeclaration, 0, len(nodes.Nodes)) + for _, e := range nodes.Nodes { + result = append(result, e.AsTypeParameter()) + } + return result +} + +func (ch *PsuedoChecker) typeFromParameter(node *ast.ParameterDeclaration) *PsuedoType { + parent := node.Parent + if parent.Kind == ast.KindSetAccessor { + return ch.GetTypeOfAccessor(parent.AsGetAccessorDeclaration()) + } + declaredType := node.Type + if declaredType != nil { + return NewPsuedoTypeDirect(declaredType) + } + if node.Initializer != nil && ast.IsIdentifier(node.Name()) && !isContextuallyTyped(node.AsNode()) { + return ch.typeFromExpression(node.Initializer) + } + // TODO: In strada, the ID checker doesn't infer a parameter type from binding pattern names, but the real checker _does_! + // This means ID won't let you write, say, `({elem}) => false` without an annotation, even though it's trivially of type + // `(p0: {elem: any}) => boolean` and error-free under `noImplicitAny: false`! + // That limitation is retained here. + return NewPsuedoTypeInferred(node.AsNode()) +} + +func (ch *PsuedoChecker) cloneParameters(nodes *ast.NodeList) []*PsuedoParameter { + if nodes == nil { + return nil + } + if len(nodes.Nodes) == 0 { + return nil + } + result := make([]*PsuedoParameter, 0, len(nodes.Nodes)) + for _, e := range nodes.Nodes { + result = append(result, NewPsuedoParameter( + e.AsParameterDeclaration().DotDotDotToken != nil, + e.Name(), + e.AsParameterDeclaration().QuestionToken != nil, + ch.typeFromParameter(e.AsParameterDeclaration()), + )) + } + return result +} + +func isContextuallyTyped(node *ast.Node) bool { + return ast.FindAncestor(node.Parent, func(n *ast.Node) bool { + // Functions calls or parent type annotations (but not the return type of a function expression) may impact the inferred type and local inference is unreliable + if ast.IsCallExpression(n) { + return true + } + if ast.IsFunctionLikeDeclaration(n) { + return n.FunctionLikeData().Type != nil || n.FunctionLikeData().FullSignature != nil + } + return ast.IsJsxElement(n) || ast.IsJsxExpression(n) + }) != nil +} diff --git a/internal/psuedochecker/type.go b/internal/psuedochecker/type.go new file mode 100644 index 0000000000..b64a4eb518 --- /dev/null +++ b/internal/psuedochecker/type.go @@ -0,0 +1,273 @@ +package psuedochecker + +import ( + "github.com/microsoft/typescript-go/internal/ast" +) + +// `PsuedoType`s are skeletons of types - partially interpreted expressions and type nodes +// composed to represent how you *should* construct a type out of them. They can be trivially +// mapped into actual types by a real `Checker`, or into a tree of `Node`s directly, without +// needing to make any intermediate types, by a `NodeBuilder`. Unlike checker `Type`s, these are +// never normalized, and multiple psuedo-types may refer to the same underlying `Type`. + +// In strada, these were implicit in the AST nodes constructed in `expressionToTypeNode.ts`, which +// repurposed AST nodes for this purpose, but in so doing, often confused weather or not it had validated +// nested nodes for use at a given use-site. By keeping the mapping deferred like this, we can know we haven't +// done any use-site checks until we're ready to map the `PsuedoType` into a `Node`, and can cache +// `PsuedoType`s across multiple target positions. + +type PsuedoTypeKind int16 + +const ( + PsuedoTypeKindDirect PsuedoTypeKind = iota + PsuedoTypeKindInferred + PsuedoTypeKindNoResult + PsuedoTypeKindUnion + PsuedoTypeKindUndefined + PsuedoTypeKindNull + PsuedoTypeKindAny + PsuedoTypeKindString + PsuedoTypeKindNumber + PsuedoTypeKindBigInt + PsuedoTypeKindBoolean + PsuedoTypeKindFalse + PsuedoTypeKindTrue + PsuedoTypeKindSingleCallSignature + PsuedoTypeKindTuple + PsuedoTypeKindObjectLiteral + PsuedoTypeKindStringLiteral + PsuedoTypeKindNumericLiteral + PsuedoTypeKindBigIntLiteral +) + +type PsuedoType struct { + Kind PsuedoTypeKind + Data psuedoTypeData +} + +func NewPsuedoType(kind PsuedoTypeKind, data psuedoTypeData) *PsuedoType { + n := data.AsPsuedoType() + n.Kind = kind + return n +} + +type psuedoTypeData interface { + AsPsuedoType() *PsuedoType +} + +type PsuedoTypeDefault struct { + PsuedoType +} + +func (b *PsuedoTypeDefault) AsPsuedoType() *PsuedoType { return &b.PsuedoType } + +type PsuedoTypeBase struct { + PsuedoTypeDefault +} + +var ( + PsuedoTypeUndefined = NewPsuedoType(PsuedoTypeKindUndefined, &PsuedoTypeBase{}) + PsuedoTypeNull = NewPsuedoType(PsuedoTypeKindNull, &PsuedoTypeBase{}) + PsuedoTypeAny = NewPsuedoType(PsuedoTypeKindAny, &PsuedoTypeBase{}) + PsuedoTypeString = NewPsuedoType(PsuedoTypeKindString, &PsuedoTypeBase{}) + PsuedoTypeNumber = NewPsuedoType(PsuedoTypeKindNumber, &PsuedoTypeBase{}) + PsuedoTypeBigInt = NewPsuedoType(PsuedoTypeKindBigInt, &PsuedoTypeBase{}) + PsuedoTypeBoolean = NewPsuedoType(PsuedoTypeKindBoolean, &PsuedoTypeBase{}) + PsuedoTypeFalse = NewPsuedoType(PsuedoTypeKindFalse, &PsuedoTypeBase{}) + PsuedoTypeTrue = NewPsuedoType(PsuedoTypeKindTrue, &PsuedoTypeBase{}) +) + +// PsuedoTypeDirect directly encodes the type referred to by a given TypeNode +type PsuedoTypeDirect struct { + PsuedoTypeBase + TypeNode *ast.Node +} + +func NewPsuedoTypeDirect(typeNode *ast.Node) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindDirect, &PsuedoTypeDirect{TypeNode: typeNode}) +} + +// PsuedoTypeInferred directly encodes the type referred to by a given Expression +// These represent cases where the expression was too complex for the psuedochecker. +// Most of the time, these locations will produce an error under ID. +type PsuedoTypeInferred struct { + PsuedoTypeBase + Expression *ast.Node +} + +func NewPsuedoTypeInferred(expr *ast.Node) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindInferred, &PsuedoTypeInferred{Expression: expr}) +} + +// PsuedoTypeNoResult is anlogous to PsuedoTypeInferred in that it references a case +// where the type was too complex for the psuedochecker. Rather than an expression, however, +// it is referring to the return type of a signature or declaration. +type PsuedoTypeNoResult struct { + PsuedoTypeBase + Declaration *ast.Node +} + +func NewPsuedoTypeNoResult(decl *ast.Node) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindNoResult, &PsuedoTypeNoResult{Declaration: decl}) +} + +// PsuedoTypeUnion is a collection of psudotypes joined into a union +type PsuedoTypeUnion struct { + PsuedoTypeBase + Types []*PsuedoType +} + +func NewPsuedoTypeUnion(types []*PsuedoType) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindUnion, &PsuedoTypeUnion{Types: types}) +} + +type PsuedoParameter struct { + Rest bool + Name *ast.Node + Optional bool + Type *PsuedoType +} + +func NewPsuedoParameter(isRest bool, name *ast.Node, isOptional bool, t *PsuedoType) *PsuedoParameter { + return &PsuedoParameter{Rest: isRest, Name: name, Optional: isOptional, Type: t} +} + +// PsuedoTypeSingleCallSignature represents an object type with a single call signature, like an arrow or function expression +type PsuedoTypeSingleCallSignature struct { + PsuedoTypeBase + Parameters []*PsuedoParameter + TypeParameters []*ast.TypeParameterDeclaration + ReturnType *PsuedoType +} + +func NewPsuedoTypeSingleCallSignature(parameters []*PsuedoParameter, typeParameters []*ast.TypeParameterDeclaration, returnType *PsuedoType) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindSingleCallSignature, &PsuedoTypeSingleCallSignature{ + Parameters: parameters, + TypeParameters: typeParameters, + ReturnType: returnType, + }) +} + +// PsuedoTypeTuple represents a tuple originaing from an `as const` array literal +type PsuedoTypeTuple struct { + PsuedoTypeBase + Elements []*PsuedoType +} + +func NewPsuedoTypeTuple(elements []*PsuedoType) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindTuple, &PsuedoTypeTuple{ + Elements: elements, + }) +} + +type PsuedoObjectElement struct { + Name *ast.Node + Optional bool + Kind PsuedoObjectElementKind + Data psuedoObjectElementData +} + +func (e *PsuedoObjectElement) AsPsuedoObjectElement() *PsuedoObjectElement { return e } + +type PsuedoObjectElementKind int8 + +const ( + PsuedoObjectElementKindMethod PsuedoObjectElementKind = iota + PsuedoObjectElementKindPropertyAssignment + PsuedoObjectElementKindSetAccessor + PsuedoObjectElementKindGetAccessor +) + +type psuedoObjectElementData interface { + AsPsuedoObjectElement() *PsuedoObjectElement +} + +func NewPsuedoObjectElement(kind PsuedoObjectElementKind, name *ast.Node, optional bool, data psuedoObjectElementData) *PsuedoObjectElement { + e := data.AsPsuedoObjectElement() + e.Kind = kind + e.Name = name + e.Optional = optional + return e +} + +type PsuedoObjectMethod struct { + PsuedoObjectElement + Parameters []*PsuedoParameter + ReturnType *PsuedoType +} + +func NewPsuedoObjectMethod(name *ast.Node, optional bool, parameters []*PsuedoParameter, returnType *PsuedoType) *PsuedoObjectElement { + return NewPsuedoObjectElement(PsuedoObjectElementKindMethod, name, optional, &PsuedoObjectMethod{ + Parameters: parameters, + ReturnType: returnType, + }) +} + +type PsuedoPropertyAssignment struct { + PsuedoObjectElement + Type *PsuedoType +} + +func NewPsuedoPropertyAssignment(name *ast.Node, optional bool, t *PsuedoType) *PsuedoObjectElement { + return NewPsuedoObjectElement(PsuedoObjectElementKindPropertyAssignment, name, optional, &PsuedoPropertyAssignment{ + Type: t, + }) +} + +type PsuedoSetAccessor struct { + PsuedoObjectElement + Parameter *PsuedoParameter +} + +func NewPsuedoSetAccessor(name *ast.Node, optional bool, p *PsuedoParameter) *PsuedoObjectElement { + return NewPsuedoObjectElement(PsuedoObjectElementKindSetAccessor, name, optional, &PsuedoSetAccessor{ + Parameter: p, + }) +} + +type PsuedoGetAccessor struct { + PsuedoObjectElement + Type *PsuedoType +} + +func NewPsuedoGetAccessor(name *ast.Node, optional bool, t *PsuedoType) *PsuedoObjectElement { + return NewPsuedoObjectElement(PsuedoObjectElementKindGetAccessor, name, optional, &PsuedoGetAccessor{ + Type: t, + }) +} + +// PsuedoTypeObjectLiteral represents an object type originaing from an object literal +type PsuedoTypeObjectLiteral struct { + PsuedoTypeBase + Elements []*PsuedoObjectElement +} + +func NewPsuedoTypeObjectLiteral(elements []*PsuedoObjectElement) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindObjectLiteral, &PsuedoTypeObjectLiteral{ + Elements: elements, + }) +} + +// PsuedoTypeLiteral represents a literal type +type PsuedoTypeLiteral struct { + PsuedoTypeBase + Node *ast.Node +} + +func NewPsuedoTypeStringLiteral(node *ast.Node) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindStringLiteral, &PsuedoTypeLiteral{ + Node: node, + }) +} + +func NewPsuedoTypeNumericLiteral(node *ast.Node) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindNumericLiteral, &PsuedoTypeLiteral{ + Node: node, + }) +} + +func NewPsuedoTypeBigIntLiteral(node *ast.Node) *PsuedoType { + return NewPsuedoType(PsuedoTypeKindBigIntLiteral, &PsuedoTypeLiteral{ + Node: node, + }) +} diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 0702913b00..58bfa7ed4a 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -1575,7 +1575,7 @@ func (tx *DeclarationTransformer) ensureParameter(p *ast.ParameterDeclaration) * func (tx *DeclarationTransformer) ensureNoInitializer(node *ast.Node) *ast.Node { if tx.shouldPrintWithInitializer(node) { unwrappedInitializer := unwrapParenthesizedExpression(node.Initializer()) - if !isPrimitiveLiteralValue(unwrappedInitializer, true) { + if !ast.IsPrimitiveLiteralValue(unwrappedInitializer, true) { tx.tracker.ReportInferenceFallback(node) } return tx.resolver.CreateLiteralConstValue(tx.EmitContext(), tx.EmitContext().ParseNode(node), tx.tracker) diff --git a/internal/transformers/declarations/util.go b/internal/transformers/declarations/util.go index e6d71f5697..a5abedb169 100644 --- a/internal/transformers/declarations/util.go +++ b/internal/transformers/declarations/util.go @@ -160,31 +160,6 @@ func unwrapParenthesizedExpression(o *ast.Node) *ast.Node { return o } -func isPrimitiveLiteralValue(node *ast.Node, includeBigInt bool) bool { - // !!! Debug.type(node); - switch node.Kind { - case ast.KindTrueKeyword, - ast.KindFalseKeyword, - ast.KindNumericLiteral, - ast.KindStringLiteral, - ast.KindNoSubstitutionTemplateLiteral: - return true - case ast.KindBigIntLiteral: - return includeBigInt - case ast.KindPrefixUnaryExpression: - if node.AsPrefixUnaryExpression().Operator == ast.KindMinusToken { - return ast.IsNumericLiteral(node.AsPrefixUnaryExpression().Operand) || (includeBigInt && ast.IsBigIntLiteral(node.AsPrefixUnaryExpression().Operand)) - } - if node.AsPrefixUnaryExpression().Operator == ast.KindPlusToken { - return ast.IsNumericLiteral(node.AsPrefixUnaryExpression().Operand) - } - return false - default: - // !!! assertType(node); - return false - } -} - func isPrivateMethodTypeParameter(host DeclarationEmitHost, node *ast.TypeParameterDeclaration) bool { return node.AsNode().Parent.Kind == ast.KindMethodDeclaration && host.GetEffectiveDeclarationFlags(node.AsNode().Parent, ast.ModifierFlagsPrivate) != 0 } From 7df30fc03dc89e1dd1f09e81eaf48c77678d25b2 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 8 Sep 2025 14:45:20 -0700 Subject: [PATCH 2/4] Stitch together psuedochecker and nodebuilder --- internal/ast/utilities.go | 24 +++ internal/checker/nodebuilder.go | 2 +- internal/checker/nodebuilderimpl.go | 44 +++- internal/checker/nodebuilderscopes.go | 13 ++ internal/checker/nodecopy.go | 190 +++++++++++++++++ internal/checker/psuedotypenodebuilder.go | 191 ++++++++++++++++++ internal/psuedochecker/lookup.go | 18 +- internal/psuedochecker/type.go | 1 + .../transformers/declarations/transform.go | 2 +- internal/transformers/declarations/util.go | 24 --- 10 files changed, 464 insertions(+), 45 deletions(-) create mode 100644 internal/checker/nodecopy.go create mode 100644 internal/checker/psuedotypenodebuilder.go diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 9839df3059..89d6cf8161 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -3940,3 +3940,27 @@ func IsPrimitiveLiteralValue(node *Node, includeBigInt bool) bool { return false } } + +func HasInferredType(node *Node) bool { + // Debug.type(node); // !!! + switch node.Kind { + case KindParameter, + KindPropertySignature, + KindPropertyDeclaration, + KindBindingElement, + KindPropertyAccessExpression, + KindElementAccessExpression, + KindBinaryExpression, + KindVariableDeclaration, + KindExportAssignment, + KindJSExportAssignment, + KindPropertyAssignment, + KindShorthandPropertyAssignment, + KindJSDocParameterTag, + KindJSDocPropertyTag: + return true + default: + // assertType(node); // !!! + return false + } +} diff --git a/internal/checker/nodebuilder.go b/internal/checker/nodebuilder.go index bd629596c7..5b801adc4a 100644 --- a/internal/checker/nodebuilder.go +++ b/internal/checker/nodebuilder.go @@ -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. diff --git a/internal/checker/nodebuilderimpl.go b/internal/checker/nodebuilderimpl.go index 832de4e14d..a88dbc5fe4 100644 --- a/internal/checker/nodebuilderimpl.go +++ b/internal/checker/nodebuilderimpl.go @@ -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/psuedochecker" "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/stringutil" "github.com/microsoft/typescript-go/internal/tspath" @@ -88,6 +89,7 @@ type nodeBuilderImpl struct { f *ast.NodeFactory ch *Checker e *printer.EmitContext + pc *psuedochecker.PsuedoChecker // cache links core.LinkStore[*ast.Node, NodeBuilderLinks] @@ -108,7 +110,7 @@ const ( // Node builder utility functions func newNodeBuilderImpl(ch *Checker, e *printer.EmitContext) *nodeBuilderImpl { - b := &nodeBuilderImpl{f: e.Factory.AsNodeFactory(), ch: ch, e: e} + b := &nodeBuilderImpl{f: e.Factory.AsNodeFactory(), ch: ch, e: e, pc: psuedochecker.NewPsuedoChecker()} b.cloneBindingNameVisitor = ast.NewNodeVisitor(b.cloneBindingName, b.f, ast.NodeVisitorHooks{}) return b } @@ -334,10 +336,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 { @@ -1549,7 +1547,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.Modifiers().Nodes, ast.IsModifier) @@ -1978,8 +1976,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) } @@ -2008,8 +2014,26 @@ 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) + if ast.IsAccessor(declaration) { + pt := b.pc.GetTypeOfAccessor(declaration) + result = b.psuedoTypeToNode(pt) + } else { + pt := b.pc.GetTypeOfDeclaration(declaration) + result = b.psuedoTypeToNode(pt) + } + remove() + } + if result == nil { + result = b.typeToTypeNode(t) + } restoreFlags() + if result == nil { + return b.f.NewKeywordTypeNode(ast.KindAnyKeyword) + } return result } @@ -2253,7 +2277,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) } diff --git a/internal/checker/nodebuilderscopes.go b/internal/checker/nodebuilderscopes.go index e6394df456..3646fd2fce 100644 --- a/internal/checker/nodebuilderscopes.go +++ b/internal/checker/nodebuilderscopes.go @@ -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, diff --git a/internal/checker/nodecopy.go b/internal/checker/nodecopy.go new file mode 100644 index 0000000000..249d6ceba3 --- /dev/null +++ b/internal/checker/nodecopy.go @@ -0,0 +1,190 @@ +package checker + +import ( + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/modulespecifiers" + "github.com/microsoft/typescript-go/internal/nodebuilder" +) + +func (b *nodeBuilderImpl) reuseNode(node *ast.Node) *ast.Node { + // !!! + return node +} + +type recoveryBoundary struct { + ctx *NodeBuilderContext + hadError bool + deferredReports []func() + oldTracker nodebuilder.SymbolTracker + oldTrackedSymbols []*TrackedSymbolArgs + oldEncounteredError bool +} + +func (b *recoveryBoundary) markError(f func()) { + b.hadError = true + if f != nil { + b.deferredReports = append(b.deferredReports, f) + } +} + +type originalRecoveryScopeState struct { + trackedSymbolsTop int + unreportedErrorsTop int + hadError bool +} + +func (b *recoveryBoundary) startRecoveryScope() originalRecoveryScopeState { + trackedSymbolsTop := len(b.ctx.trackedSymbols) + unreportedErrorsTop := len(b.deferredReports) + return originalRecoveryScopeState{trackedSymbolsTop: trackedSymbolsTop, unreportedErrorsTop: unreportedErrorsTop, hadError: b.hadError} +} + +func (b *recoveryBoundary) endRecoveryScope(state originalRecoveryScopeState) { + b.hadError = state.hadError + b.ctx.trackedSymbols = b.ctx.trackedSymbols[0:state.trackedSymbolsTop] + b.deferredReports = b.deferredReports[0:state.unreportedErrorsTop] +} + +type wrappingTracker struct { + wrapped nodebuilder.SymbolTracker + bound *recoveryBoundary +} + +func (w *wrappingTracker) GetModuleSpecifierGenerationHost() modulespecifiers.ModuleSpecifierGenerationHost { + return w.wrapped.GetModuleSpecifierGenerationHost() +} + +func (w *wrappingTracker) PopErrorFallbackNode() { + w.wrapped.PopErrorFallbackNode() +} + +func (w *wrappingTracker) PushErrorFallbackNode(node *ast.Node) { + w.wrapped.PushErrorFallbackNode(node) +} + +func (w *wrappingTracker) ReportCyclicStructureError() { + w.bound.markError(w.wrapped.ReportCyclicStructureError) +} + +func (w *wrappingTracker) ReportInaccessibleThisError() { + w.bound.markError(w.wrapped.ReportInaccessibleThisError) +} + +func (w *wrappingTracker) ReportInaccessibleUniqueSymbolError() { + w.bound.markError(w.wrapped.ReportInaccessibleUniqueSymbolError) +} + +func (w *wrappingTracker) ReportInferenceFallback(node *ast.Node) { + w.wrapped.ReportInferenceFallback(node) // Should this also be deferred? +} + +func (w *wrappingTracker) ReportLikelyUnsafeImportRequiredError(specifier string) { + w.bound.markError(func() { w.wrapped.ReportLikelyUnsafeImportRequiredError(specifier) }) +} + +func (w *wrappingTracker) ReportNonSerializableProperty(propertyName string) { + w.bound.markError(func() { w.wrapped.ReportNonSerializableProperty(propertyName) }) +} + +func (w *wrappingTracker) ReportNonlocalAugmentation(containingFile *ast.SourceFile, parentSymbol *ast.Symbol, augmentingSymbol *ast.Symbol) { + w.wrapped.ReportNonlocalAugmentation(containingFile, parentSymbol, augmentingSymbol) // Should this also be deferred? +} + +func (w *wrappingTracker) ReportPrivateInBaseOfClassExpression(propertyName string) { + w.bound.markError(func() { w.wrapped.ReportPrivateInBaseOfClassExpression(propertyName) }) +} + +func (w *wrappingTracker) ReportTruncationError() { + w.wrapped.ReportTruncationError() // Should this also be deferred? +} + +func (w *wrappingTracker) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) bool { + w.bound.ctx.trackedSymbols = append(w.bound.ctx.trackedSymbols, &TrackedSymbolArgs{symbol, enclosingDeclaration, meaning}) + return false +} + +func newWrappingTracker(inner nodebuilder.SymbolTracker, bound *recoveryBoundary) *wrappingTracker { + return &wrappingTracker{ + wrapped: inner, + bound: bound, + } +} + +func (b *nodeBuilderImpl) createRecoveryBoundary() *recoveryBoundary { + b.ch.checkNotCanceled() + bound := &recoveryBoundary{oldTracker: b.ctx.tracker, oldTrackedSymbols: b.ctx.trackedSymbols, oldEncounteredError: b.ctx.encounteredError} + newTracker := NewSymbolTrackerImpl(b.ctx, newWrappingTracker(b.ctx.tracker, bound), b.ctx.tracker.GetModuleSpecifierGenerationHost()) + b.ctx.tracker = newTracker + b.ctx.trackedSymbols = nil + return bound +} + +func (b *nodeBuilderImpl) finalizeBoundary(bound *recoveryBoundary) bool { + b.ctx.tracker = bound.oldTracker + b.ctx.trackedSymbols = bound.oldTrackedSymbols + b.ctx.encounteredError = bound.oldEncounteredError + + for _, f := range bound.deferredReports { + f() + } + if bound.hadError { + return false + } + for _, a := range b.ctx.trackedSymbols { + b.ctx.tracker.TrackSymbol(a.symbol, a.enclosingDeclaration, a.meaning) + } + return true +} + +func (b *nodeBuilderImpl) tryReuseExistingTypeNodeHelper(existing *ast.TypeNode) *ast.TypeNode { + bound := b.createRecoveryBoundary() + var transformed *ast.Node + // !!! + if !b.finalizeBoundary(bound) { + return nil + } + b.ctx.approximateLength += existing.Loc.End() - existing.Loc.Pos() + return transformed +} + +func getExistingNodeTreeVisitor(b *nodeBuilderImpl, bound *recoveryBoundary, factory *ast.NodeFactory) *ast.NodeVisitor { + visitExistingNodeTreeSymbolsWorker := func(node *ast.Node) *ast.Node { + return node // !!! + } + return ast.NewNodeVisitor(func(node *ast.Node) *ast.Node { + if bound.hadError { + return node + } + recover := bound.startRecoveryScope() + introducesNewScope := ast.IsFunctionLike(node) || ast.IsMappedTypeNode(node) + var exit func() + if introducesNewScope { + var params []*ast.Symbol + var typeParams []*Type + if ast.IsFunctionLike(node) { + sig := b.ch.getSignatureFromDeclaration(node) + params = sig.parameters + typeParams = sig.typeParameters + } else if ast.IsConditionalTypeNode(node) { // !!! TODO: impossible in combination with the scope start check??? + typeParams = b.ch.getInferTypeParameters(node) + } else if ast.IsMappedTypeNode(node) { + typeParams = []*Type{b.ch.getDeclaredTypeOfTypeParameter(b.ch.getSymbolOfDeclaration(node.AsMappedTypeNode().TypeParameter))} + } + exit = b.enterNewScope(node, params, typeParams, nil, nil) + } + result := visitExistingNodeTreeSymbolsWorker(node) + if exit != nil { + exit() + } + + if bound.hadError { + if ast.IsTypeNode(node) && !ast.IsTypePredicateNode(node) { + bound.endRecoveryScope(recover) + return nil // !!! + } + return node + } + + return result + }, factory, ast.NodeVisitorHooks{}) +} diff --git a/internal/checker/psuedotypenodebuilder.go b/internal/checker/psuedotypenodebuilder.go new file mode 100644 index 0000000000..3e229152eb --- /dev/null +++ b/internal/checker/psuedotypenodebuilder.go @@ -0,0 +1,191 @@ +package checker + +import ( + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/debug" + "github.com/microsoft/typescript-go/internal/nodebuilder" + "github.com/microsoft/typescript-go/internal/printer" + "github.com/microsoft/typescript-go/internal/psuedochecker" +) + +// Maps a psuedochecker's psuedotypes into ast nodes and reports any inference fallback errors the psuedotype structure implies +func (b *nodeBuilderImpl) psuedoTypeToNode(t *psuedochecker.PsuedoType) *ast.Node { + debug.Assert(t != nil, "Attempted to serialize nil psuedotype") + switch t.Kind { + case psuedochecker.PsuedoTypeKindDirect: + return b.reuseNode(t.Data.(*psuedochecker.PsuedoTypeDirect).TypeNode) + case psuedochecker.PsuedoTypeKindInferred: + node := t.Data.(*psuedochecker.PsuedoTypeInferred).Expression + b.ctx.tracker.ReportInferenceFallback(node) + ty := b.ch.getTypeOfExpression(node) + return b.typeToTypeNode(ty) + case psuedochecker.PsuedoTypeKindNoResult: + node := t.Data.(*psuedochecker.PsuedoTypeNoResult).Declaration + b.ctx.tracker.ReportInferenceFallback(node) + return b.serializeTypeForDeclaration(node, nil, nil, false) + case psuedochecker.PsuedoTypeKindUnion: + var res []*ast.Node + members := t.Data.(*psuedochecker.PsuedoTypeUnion).Types + for _, m := range members { + // TODO: improvement - in non-strict-null-checks mode, elide `undefined` and `null` in unions, instead of mapping them to `any` + res = append(res, b.psuedoTypeToNode(m)) + } + return b.f.NewUnionTypeNode(b.f.NewNodeList(res)) + case psuedochecker.PsuedoTypeKindUndefined: + if !b.ch.strictNullChecks { + return b.f.NewKeywordTypeNode(ast.KindAnyKeyword) + } + return b.f.NewKeywordTypeNode(ast.KindUndefinedKeyword) + case psuedochecker.PsuedoTypeKindNull: + if !b.ch.strictNullChecks { + return b.f.NewKeywordTypeNode(ast.KindAnyKeyword) + } + return b.f.NewKeywordTypeNode(ast.KindNullKeyword) + case psuedochecker.PsuedoTypeKindAny: + return b.f.NewKeywordTypeNode(ast.KindAnyKeyword) + case psuedochecker.PsuedoTypeKindString: + return b.f.NewKeywordTypeNode(ast.KindStringKeyword) + case psuedochecker.PsuedoTypeKindNumber: + return b.f.NewKeywordTypeNode(ast.KindNumberKeyword) + case psuedochecker.PsuedoTypeKindBigInt: + return b.f.NewKeywordTypeNode(ast.KindBigIntKeyword) + case psuedochecker.PsuedoTypeKindBoolean: + return b.f.NewKeywordTypeNode(ast.KindBooleanKeyword) + case psuedochecker.PsuedoTypeKindFalse: + return b.f.NewKeywordTypeNode(ast.KindFalseKeyword) + case psuedochecker.PsuedoTypeKindTrue: + return b.f.NewKeywordTypeNode(ast.KindTrueKeyword) + case psuedochecker.PsuedoTypeKindSingleCallSignature: + d := t.Data.(*psuedochecker.PsuedoTypeSingleCallSignature) + var typeParams *ast.NodeList + if len(d.TypeParameters) > 0 { + res := make([]*ast.Node, 0, len(d.TypeParameters)) + for _, tp := range d.TypeParameters { + res = append(res, b.reuseNode(tp.AsNode())) + } + typeParams = b.f.NewNodeList(res) + } + params := b.psuedoParametersToNodeList(d.Parameters) + returnType := b.psuedoTypeToNode(d.ReturnType) + return b.f.NewFunctionTypeNode(typeParams, params, returnType) + case psuedochecker.PsuedoTypeKindTuple: + var res []*ast.Node + elements := t.Data.(*psuedochecker.PsuedoTypeTuple).Elements + for _, e := range elements { + res = append(res, b.psuedoTypeToNode(e)) + } + // !!! TODO: psuedo-tuples are implicitly `readonly` since they originate from `as const` contexts + // but strada fails to add the `readonly` modifier to the generated node. We replicate that bug here. + // return b.f.NewTypeOperatorNode(ast.KindReadonlyKeyword, b.f.NewTupleTypeNode(b.f.NewNodeList(res))) + result := b.f.NewTupleTypeNode(b.f.NewNodeList(res)) + b.e.AddEmitFlags(result, printer.EFSingleLine) + return result + case psuedochecker.PsuedoTypeKindObjectLiteral: + elements := t.Data.(*psuedochecker.PsuedoTypeObjectLiteral).Elements + if len(elements) == 0 { + result := b.f.NewTypeLiteralNode(b.f.NewNodeList(nil)) + b.e.AddEmitFlags(result, printer.EFSingleLine) + return result + } + // NOTE: using the checker's `isConstContext` instead of the psuedochecker's `isInConstContext` + // results in different results here. The checker one is more "correct" but means we'll mark + // objects in parameter positions contextually typed by const type parameters as readonly - + // something a true syntactic ID emitter couldn't possibly know (since the signature could + // be from across files). This can't *really* happen in any cases ID doesn't already error on, though. + // Just something to keep in mind if the ID checker keeps growing. + isConst := b.ch.isConstContext(elements[0].Name) + newElements := make([]*ast.Node, 0, len(elements)) + // TODO: strada's ID logic is piecemeal in `name` reuse validation - only methods remap `new` to `"new"` + // we should have a unified `reuseName` codepath that remaps keyword ID names to string literal names + for _, e := range elements { + var modifiers *ast.ModifierList + if isConst { + modifiers = b.f.NewModifierList([]*ast.Node{b.f.NewModifier(ast.KindReadonlyKeyword)}) + } + var newProp *ast.Node + switch e.Kind { + case psuedochecker.PsuedoObjectElementKindMethod: + d := e.Data.(*psuedochecker.PsuedoObjectMethod) + newProp = b.f.NewMethodSignatureDeclaration( + modifiers, + b.reuseNode(e.Name), + nil, + nil, + b.psuedoParametersToNodeList(d.Parameters), + b.psuedoTypeToNode(d.ReturnType), + ) + case psuedochecker.PsuedoObjectElementKindPropertyAssignment: + d := e.Data.(*psuedochecker.PsuedoPropertyAssignment) + newProp = b.f.NewPropertySignatureDeclaration( + modifiers, + b.reuseNode(e.Name), + nil, + b.psuedoTypeToNode(d.Type), + nil, + ) + case psuedochecker.PsuedoObjectElementKindSetAccessor: + d := e.Data.(*psuedochecker.PsuedoSetAccessor) + newProp = b.f.NewSetAccessorDeclaration( + nil, + b.reuseNode(e.Name), + nil, + b.f.NewNodeList([]*ast.Node{b.psuedoParameterToNode(d.Parameter)}), + nil, + nil, + nil, + ) + case psuedochecker.PsuedoObjectElementKindGetAccessor: + d := e.Data.(*psuedochecker.PsuedoGetAccessor) + newProp = b.f.NewSetAccessorDeclaration( + nil, + b.reuseNode(e.Name), + nil, + nil, + b.psuedoTypeToNode(d.Type), + nil, + nil, + ) + } + b.e.SetCommentRange(newProp, e.Name.Parent.Loc) + newElements = append(newElements, newProp) + } + result := b.f.NewTypeLiteralNode(b.f.NewNodeList(newElements)) + if b.ctx.flags&nodebuilder.FlagsMultilineObjectLiterals == 0 { + b.e.AddEmitFlags(result, printer.EFSingleLine) + } + return result + case psuedochecker.PsuedoTypeKindStringLiteral, psuedochecker.PsuedoTypeKindNumericLiteral, psuedochecker.PsuedoTypeKindBigIntLiteral: + source := t.Data.(*psuedochecker.PsuedoTypeLiteral).Node + return b.f.NewLiteralTypeNode(b.reuseNode(source)) + default: + debug.AssertNever(t.Kind, "Unhandled psuedotype kind in psuedotype node construction") + return nil + } +} + +func (b *nodeBuilderImpl) psuedoParametersToNodeList(params []*psuedochecker.PsuedoParameter) *ast.NodeList { + res := make([]*ast.Node, 0, len(params)) + for _, p := range params { + res = append(res, b.psuedoParameterToNode(p)) + } + return b.f.NewNodeList(res) +} + +func (b *nodeBuilderImpl) psuedoParameterToNode(p *psuedochecker.PsuedoParameter) *ast.Node { + var dotDotDot *ast.Node + var questionMark *ast.Node + if p.Rest { + dotDotDot = b.f.NewToken(ast.KindDotDotDotToken) + } + if p.Optional { + questionMark = b.f.NewToken(ast.KindQuestionToken) + } + return b.f.NewParameterDeclaration( + nil, + dotDotDot, + b.reuseNode(p.Name), + questionMark, + b.psuedoTypeToNode(p.Type), + nil, + ) +} diff --git a/internal/psuedochecker/lookup.go b/internal/psuedochecker/lookup.go index b9d757d967..fe5a65026d 100644 --- a/internal/psuedochecker/lookup.go +++ b/internal/psuedochecker/lookup.go @@ -9,7 +9,7 @@ import ( func (ch *PsuedoChecker) GetReturnTypeOfSignature(signatureNode *ast.Node) *PsuedoType { switch signatureNode.Kind { case ast.KindGetAccessor: - return ch.GetTypeOfAccessor(signatureNode.AsGetAccessorDeclaration()) + return ch.GetTypeOfAccessor(signatureNode) case ast.KindMethodDeclaration, ast.KindFunctionDeclaration, ast.KindConstructor, ast.KindMethodSignature, ast.KindCallSignature, ast.KindConstructSignature, ast.KindSetAccessor, ast.KindIndexSignature, ast.KindFunctionType, ast.KindConstructorType, @@ -21,10 +21,10 @@ func (ch *PsuedoChecker) GetReturnTypeOfSignature(signatureNode *ast.Node) *Psue } } -func (ch *PsuedoChecker) GetTypeOfAccessor(accessor *ast.GetAccessorDeclaration) *PsuedoType { +func (ch *PsuedoChecker) GetTypeOfAccessor(accessor *ast.Node) *PsuedoType { annotated := ch.typeFromAccessor(accessor) if annotated.Kind == PsuedoTypeKindNoResult { - return ch.inferAccessorType(accessor.AsNode()) + return ch.inferAccessorType(accessor) } return annotated } @@ -106,16 +106,16 @@ func (ch *PsuedoChecker) typeFromVariable(declaration *ast.VariableDeclaration) return NewPsuedoTypeNoResult(declaration.AsNode()) } -func (ch *PsuedoChecker) typeFromAccessor(accessor *ast.GetAccessorDeclaration) *PsuedoType { - accessorDeclarations := ast.GetAllAccessorDeclarationsForDeclaration(accessor.AsNode(), accessor.Symbol) - accessorType := ch.getTypeAnnotationFromAllAccessorDeclarations(accessor.AsNode(), accessorDeclarations) +func (ch *PsuedoChecker) typeFromAccessor(accessor *ast.Node) *PsuedoType { + accessorDeclarations := ast.GetAllAccessorDeclarationsForDeclaration(accessor, accessor.DeclarationData().Symbol) + accessorType := ch.getTypeAnnotationFromAllAccessorDeclarations(accessor, accessorDeclarations) if accessorType != nil && !ast.IsTypePredicateNode(accessorType) { return NewPsuedoTypeDirect(accessorType) } if accessorDeclarations.GetAccessor != nil { return ch.createReturnFromSignature(accessorDeclarations.GetAccessor.AsNode()) } - return NewPsuedoTypeNoResult(accessor.AsNode()) + return NewPsuedoTypeNoResult(accessor) } func (ch *PsuedoChecker) inferAccessorType(node *ast.Node) *PsuedoType { @@ -334,7 +334,7 @@ func (ch *PsuedoChecker) typeFromObjectLiteral(node *ast.ObjectLiteralExpression results = append(results, NewPsuedoGetAccessor( e.Name(), false, - ch.typeFromAccessor(e.AsGetAccessorDeclaration()), + ch.typeFromAccessor(e), )) } } @@ -471,7 +471,7 @@ func (ch *PsuedoChecker) cloneTypeParameters(nodes *ast.NodeList) []*ast.TypePar func (ch *PsuedoChecker) typeFromParameter(node *ast.ParameterDeclaration) *PsuedoType { parent := node.Parent if parent.Kind == ast.KindSetAccessor { - return ch.GetTypeOfAccessor(parent.AsGetAccessorDeclaration()) + return ch.GetTypeOfAccessor(parent) } declaredType := node.Type if declaredType != nil { diff --git a/internal/psuedochecker/type.go b/internal/psuedochecker/type.go index b64a4eb518..87ca142c2e 100644 --- a/internal/psuedochecker/type.go +++ b/internal/psuedochecker/type.go @@ -48,6 +48,7 @@ type PsuedoType struct { func NewPsuedoType(kind PsuedoTypeKind, data psuedoTypeData) *PsuedoType { n := data.AsPsuedoType() n.Kind = kind + n.Data = data return n } diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 58bfa7ed4a..6d69ec9eee 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -1014,7 +1014,7 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) } var typeNode *ast.Node - if hasInferredType(node) { + if ast.HasInferredType(node) { typeNode = tx.resolver.CreateTypeOfDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) } else if ast.IsFunctionLike(node) { typeNode = tx.resolver.CreateReturnTypeOfSignatureDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) diff --git a/internal/transformers/declarations/util.go b/internal/transformers/declarations/util.go index a5abedb169..7c72216411 100644 --- a/internal/transformers/declarations/util.go +++ b/internal/transformers/declarations/util.go @@ -48,30 +48,6 @@ func canProduceDiagnostics(node *ast.Node) bool { /* ast.IsJSDocTypeAlias(node); */ } -func hasInferredType(node *ast.Node) bool { - // Debug.type(node); // !!! - switch node.Kind { - case ast.KindParameter, - ast.KindPropertySignature, - ast.KindPropertyDeclaration, - ast.KindBindingElement, - ast.KindPropertyAccessExpression, - ast.KindElementAccessExpression, - ast.KindBinaryExpression, - ast.KindVariableDeclaration, - ast.KindExportAssignment, - ast.KindJSExportAssignment, - ast.KindPropertyAssignment, - ast.KindShorthandPropertyAssignment, - ast.KindJSDocParameterTag, - ast.KindJSDocPropertyTag: - return true - default: - // assertType(node); // !!! - return false - } -} - func isDeclarationAndNotVisible(emitContext *printer.EmitContext, resolver printer.EmitResolver, node *ast.Node) bool { node = emitContext.ParseNode(node) switch node.Kind { From febbd9ef18712a8158eb382166bd285b31856434 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 8 Sep 2025 15:43:47 -0700 Subject: [PATCH 3/4] panic fixes --- internal/checker/nodecopy.go | 3 +-- internal/checker/psuedotypenodebuilder.go | 10 ++++++---- internal/psuedochecker/lookup.go | 4 ++-- internal/psuedochecker/type.go | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/checker/nodecopy.go b/internal/checker/nodecopy.go index 249d6ceba3..5d0ed1cecf 100644 --- a/internal/checker/nodecopy.go +++ b/internal/checker/nodecopy.go @@ -7,8 +7,7 @@ import ( ) func (b *nodeBuilderImpl) reuseNode(node *ast.Node) *ast.Node { - // !!! - return node + return b.f.DeepCloneNode(node) // !!! TODO: validate refs, apply some .Locs from original nodes where safe } type recoveryBoundary struct { diff --git a/internal/checker/psuedotypenodebuilder.go b/internal/checker/psuedotypenodebuilder.go index 3e229152eb..fbb3f958ae 100644 --- a/internal/checker/psuedotypenodebuilder.go +++ b/internal/checker/psuedotypenodebuilder.go @@ -40,7 +40,7 @@ func (b *nodeBuilderImpl) psuedoTypeToNode(t *psuedochecker.PsuedoType) *ast.Nod if !b.ch.strictNullChecks { return b.f.NewKeywordTypeNode(ast.KindAnyKeyword) } - return b.f.NewKeywordTypeNode(ast.KindNullKeyword) + return b.f.NewLiteralTypeNode(b.f.NewKeywordExpression(ast.KindNullKeyword)) case psuedochecker.PsuedoTypeKindAny: return b.f.NewKeywordTypeNode(ast.KindAnyKeyword) case psuedochecker.PsuedoTypeKindString: @@ -52,9 +52,9 @@ func (b *nodeBuilderImpl) psuedoTypeToNode(t *psuedochecker.PsuedoType) *ast.Nod case psuedochecker.PsuedoTypeKindBoolean: return b.f.NewKeywordTypeNode(ast.KindBooleanKeyword) case psuedochecker.PsuedoTypeKindFalse: - return b.f.NewKeywordTypeNode(ast.KindFalseKeyword) + return b.f.NewLiteralTypeNode(b.f.NewKeywordExpression(ast.KindFalseKeyword)) case psuedochecker.PsuedoTypeKindTrue: - return b.f.NewKeywordTypeNode(ast.KindTrueKeyword) + return b.f.NewLiteralTypeNode(b.f.NewKeywordExpression(ast.KindTrueKeyword)) case psuedochecker.PsuedoTypeKindSingleCallSignature: d := t.Data.(*psuedochecker.PsuedoTypeSingleCallSignature) var typeParams *ast.NodeList @@ -146,7 +146,9 @@ func (b *nodeBuilderImpl) psuedoTypeToNode(t *psuedochecker.PsuedoType) *ast.Nod nil, ) } - b.e.SetCommentRange(newProp, e.Name.Parent.Loc) + if b.ctx.enclosingFile == ast.GetSourceFileOfNode(e.Name) { + b.e.SetCommentRange(newProp, e.Name.Parent.Loc) + } newElements = append(newElements, newProp) } result := b.f.NewTypeLiteralNode(b.f.NewNodeList(newElements)) diff --git a/internal/psuedochecker/lookup.go b/internal/psuedochecker/lookup.go index fe5a65026d..c40d6aa01d 100644 --- a/internal/psuedochecker/lookup.go +++ b/internal/psuedochecker/lookup.go @@ -43,7 +43,7 @@ func (ch *PsuedoChecker) GetTypeOfDeclaration(node *ast.Node) *PsuedoType { return ch.typeFromProperty(node) case ast.KindBindingElement: return NewPsuedoTypeNoResult(node) - case ast.KindExportAssignment: + case ast.KindExportAssignment, ast.KindJSExportAssignment: return ch.typeFromExpression(node.AsExportAssignment().Expression) case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression, ast.KindBinaryExpression: return ch.typeFromExpandoProperty(node) @@ -412,7 +412,7 @@ func (ch *PsuedoChecker) isInConstContext(node *ast.Node) bool { } func (ch *PsuedoChecker) typeFromPrimitiveLiteralPrefix(node *ast.PrefixUnaryExpression) *PsuedoType { - inner := node.Expression() + inner := node.Operand if inner.Kind == ast.KindBigIntLiteral { if ch.isInConstContext(node.AsNode()) { return NewPsuedoTypeBigIntLiteral(node.AsNode()) diff --git a/internal/psuedochecker/type.go b/internal/psuedochecker/type.go index 87ca142c2e..8ed9f99433 100644 --- a/internal/psuedochecker/type.go +++ b/internal/psuedochecker/type.go @@ -188,6 +188,7 @@ func NewPsuedoObjectElement(kind PsuedoObjectElementKind, name *ast.Node, option e.Kind = kind e.Name = name e.Optional = optional + e.Data = data return e } From f734c03f22e0373e4a8244ac3f013d1598f5d275 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 8 Sep 2025 16:12:22 -0700 Subject: [PATCH 4/4] purge one last test panic --- internal/checker/checker.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index b3e0705e6e..40e9e0358d 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -11012,6 +11012,11 @@ func (c *Checker) checkPrivateIdentifierPropertyAccess(leftType *Type, right *as } func (c *Checker) reportNonexistentProperty(propNode *ast.Node, containingType *Type) { + links := c.nodeLinks.Get(propNode) + if links.flags&NodeCheckFlagsTypeChecked != 0 { + return // error already made/in progress + } + links.flags |= NodeCheckFlagsTypeChecked var diagnostic *ast.Diagnostic if !ast.IsPrivateIdentifier(propNode) && containingType.flags&TypeFlagsUnion != 0 && containingType.flags&TypeFlagsPrimitive == 0 { for _, subtype := range containingType.Types() {