vendor tsgo
This commit is contained in:
729
tools/tsgo/internal/pseudochecker/lookup.go
Normal file
729
tools/tsgo/internal/pseudochecker/lookup.go
Normal file
@@ -0,0 +1,729 @@
|
||||
package pseudochecker
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/debug"
|
||||
)
|
||||
|
||||
func (ch *PseudoChecker) GetReturnTypeOfSignature(signatureNode *ast.Node) *PseudoType {
|
||||
switch signatureNode.Kind {
|
||||
case ast.KindGetAccessor:
|
||||
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,
|
||||
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 *PseudoChecker) GetTypeOfAccessor(accessor *ast.Node) *PseudoType {
|
||||
return ch.typeFromAccessor(accessor)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) GetTypeOfExpression(node *ast.Node) *PseudoType {
|
||||
return ch.typeFromExpression(node)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) GetTypeOfDeclaration(node *ast.Node) *PseudoType {
|
||||
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 NewPseudoTypeNoResult(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)
|
||||
case ast.KindCallExpression:
|
||||
switch ast.GetAssignmentDeclarationKind(node) {
|
||||
// TODO: How much of the checker's getTypeFromPropertyDescriptor is worth trying to emulate over ASTs?
|
||||
case ast.JSDeclarationKindObjectDefinePropertyValue:
|
||||
{
|
||||
// !!!
|
||||
}
|
||||
case ast.JSDeclarationKindObjectDefinePropertyExports:
|
||||
{
|
||||
// !!!
|
||||
}
|
||||
}
|
||||
return NewPseudoTypeNoResult(node)
|
||||
default:
|
||||
debug.FailBadSyntaxKind(node, "node needs to be an inferrable node")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromPropertyAssignment(node *ast.Node) *PseudoType {
|
||||
annotation := node.Type()
|
||||
if annotation != nil {
|
||||
return NewPseudoTypeDirect(annotation)
|
||||
}
|
||||
if node.Kind == ast.KindPropertyAssignment {
|
||||
init := node.Initializer()
|
||||
if init != nil {
|
||||
expr := ch.typeFromExpression(init)
|
||||
if expr != nil && (expr.Kind != PseudoTypeKindInferred || len(expr.AsPseudoTypeInferred().ErrorNodes) > 0) {
|
||||
return expr
|
||||
}
|
||||
// fallback to NoResult if PseudoTypeKindInferred without error nodes
|
||||
}
|
||||
}
|
||||
return NewPseudoTypeNoResult(node)
|
||||
}
|
||||
|
||||
// This is _not_ redundant with the reparser; see how expandoFunctionSymbolProperty.ts and similar behaves
|
||||
func (ch *PseudoChecker) typeFromExpandoProperty(node *ast.Node) *PseudoType {
|
||||
declaredType := node.Type()
|
||||
if declaredType != nil {
|
||||
return NewPseudoTypeDirect(declaredType)
|
||||
}
|
||||
// While `node` is an expression, as an expando, it should also always be a
|
||||
// declaration with a `.Symbol()` which requires declaration fallback handling
|
||||
return NewPseudoTypeNoResult(node)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromProperty(node *ast.Node) *PseudoType {
|
||||
t := node.Type()
|
||||
if t != nil {
|
||||
return NewPseudoTypeDirect(t)
|
||||
}
|
||||
if ast.IsPropertyDeclaration(node) {
|
||||
init := node.Initializer()
|
||||
if init != nil && !isContextuallyTyped(node) {
|
||||
// explicit fail on readonly template literals to allow for literal freshness in the future
|
||||
if ast.HasModifier(node, ast.ModifierFlagsReadonly) && ast.IsTemplateExpression(init) {
|
||||
return NewPseudoTypeNoResult(node)
|
||||
}
|
||||
expr := ch.typeFromExpression(init)
|
||||
if expr != nil && (expr.Kind != PseudoTypeKindInferred || len(expr.AsPseudoTypeInferred().ErrorNodes) > 0) {
|
||||
if expr.Kind != PseudoTypeKindDirect && node.AsPropertyDeclaration().PostfixToken != nil && node.AsPropertyDeclaration().PostfixToken.Kind == ast.KindQuestionToken {
|
||||
// type comes from the initializer expression on a property with a `?` - add `| undefined` to the type
|
||||
return addUndefinedIfDefinitelyRequired(expr)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
// fallback to NoResult if PseudoTypeKindInferred without error nodes
|
||||
}
|
||||
}
|
||||
return NewPseudoTypeNoResult(node)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromVariable(declaration *ast.VariableDeclaration) *PseudoType {
|
||||
t := declaration.Type
|
||||
if t != nil {
|
||||
return NewPseudoTypeDirect(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
|
||||
// TODO: Strada forces an inference fallback on `const` variables with template expression initializers, to leave space for template literal freshness in the future
|
||||
if ast.IsVarConst(declaration.AsNode()) && ast.IsTemplateExpression(init) {
|
||||
return NewPseudoTypeNoResult(declaration.AsNode())
|
||||
}
|
||||
expr := ch.typeFromExpression(init)
|
||||
if expr != nil && (expr.Kind != PseudoTypeKindInferred || len(expr.AsPseudoTypeInferred().ErrorNodes) > 0) {
|
||||
return expr
|
||||
}
|
||||
// fallback to NoResult if PseudoTypeKindInferred without error nodes
|
||||
}
|
||||
}
|
||||
return NewPseudoTypeNoResult(declaration.AsNode())
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromAccessor(accessor *ast.Node) *PseudoType {
|
||||
accessorDeclarations := ast.GetAllAccessorDeclarationsForDeclaration(accessor, accessor.DeclarationData().Symbol.Declarations)
|
||||
accessorType := ch.getTypeAnnotationFromAllAccessorDeclarations(accessor, accessorDeclarations)
|
||||
if accessorType != nil && !ast.IsTypePredicateNode(accessorType) {
|
||||
return NewPseudoTypeDirect(accessorType)
|
||||
}
|
||||
if accessorDeclarations.GetAccessor != nil {
|
||||
res := ch.createReturnFromSignature(accessorDeclarations.GetAccessor.AsNode())
|
||||
if res.Kind == PseudoTypeKindInferred && len(res.AsPseudoTypeInferred().ErrorNodes) == 0 {
|
||||
errorNodes := []*ast.Node{accessorDeclarations.GetAccessor.AsNode()}
|
||||
if accessorDeclarations.SetAccessor != nil {
|
||||
errorNodes = append(errorNodes, accessorDeclarations.SetAccessor.AsNode())
|
||||
}
|
||||
res = NewPseudoTypeInferredWithErrors(res.AsPseudoTypeInferred().Expression, res.AsPseudoTypeInferred().IsSignatureReturn, errorNodes) // Move error up to the accessor
|
||||
}
|
||||
return res
|
||||
}
|
||||
return NewPseudoTypeNoResult(accessor)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) 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 *PseudoChecker) 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.IsParameterDeclaration(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` pseudotype instead
|
||||
func (ch *PseudoChecker) createReturnFromSignature(fn *ast.Node) *PseudoType {
|
||||
if ast.IsFunctionLike(fn) {
|
||||
d := fn.FunctionLikeData()
|
||||
// !!! TODO: support ripping return type off of .FullSignature
|
||||
r := d.Type
|
||||
if r != nil {
|
||||
return NewPseudoTypeDirect(r)
|
||||
}
|
||||
}
|
||||
if isValueSignatureDeclaration(fn) {
|
||||
return ch.typeFromSingleReturnExpression(fn)
|
||||
}
|
||||
return NewPseudoTypeNoResult(fn)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromSingleReturnExpression(fn *ast.Node) *PseudoType {
|
||||
var candidateExpr *ast.Node
|
||||
if fn != nil && !ast.NodeIsMissing(fn.Body()) {
|
||||
flags := ast.GetFunctionFlags(fn)
|
||||
if flags&ast.FunctionFlagsAsyncGenerator != 0 {
|
||||
return NewPseudoTypeInferred(fn, true)
|
||||
}
|
||||
|
||||
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 NewPseudoTypeDirect(t)
|
||||
}
|
||||
} else {
|
||||
return ch.typeFromExpression(candidateExpr)
|
||||
}
|
||||
}
|
||||
return NewPseudoTypeInferred(fn, true)
|
||||
}
|
||||
|
||||
// This is basically `checkExpression` for pseudotypes
|
||||
func (ch *PseudoChecker) typeFromExpression(node *ast.Node) *PseudoType {
|
||||
switch node.Kind {
|
||||
case ast.KindOmittedExpression:
|
||||
return PseudoTypeUndefined
|
||||
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 pseudoglobals for that to work!
|
||||
if node.AsIdentifier().Text == "undefined" {
|
||||
return PseudoTypeUndefined
|
||||
}
|
||||
case ast.KindNullKeyword:
|
||||
return PseudoTypeNull
|
||||
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 NewPseudoTypeInferredWithErrors(node, false, []*ast.Node{node}) // No possible annotation/directly mappable syntax
|
||||
case ast.KindTemplateExpression:
|
||||
// templateLitWithHoles as const, not supported
|
||||
if IsInConstContext(node) {
|
||||
return NewPseudoTypeInferred(node, false)
|
||||
}
|
||||
return NewPseudoTypeMaybeConstLocation(node, NewPseudoTypeInferred(node, false), PseudoTypeString)
|
||||
case ast.KindNumericLiteral:
|
||||
return NewPseudoTypeMaybeConstLocation(node, NewPseudoTypeNumericLiteral(node), PseudoTypeNumber)
|
||||
case ast.KindNoSubstitutionTemplateLiteral:
|
||||
return NewPseudoTypeMaybeConstLocation(node, NewPseudoTypeStringLiteral(node), PseudoTypeString)
|
||||
case ast.KindStringLiteral:
|
||||
return NewPseudoTypeMaybeConstLocation(node, NewPseudoTypeStringLiteral(node), PseudoTypeString)
|
||||
case ast.KindBigIntLiteral:
|
||||
return NewPseudoTypeMaybeConstLocation(node, NewPseudoTypeBigIntLiteral(node), PseudoTypeBigInt)
|
||||
case ast.KindTrueKeyword:
|
||||
return NewPseudoTypeMaybeConstLocation(node, PseudoTypeTrue, PseudoTypeBoolean)
|
||||
case ast.KindFalseKeyword:
|
||||
return NewPseudoTypeMaybeConstLocation(node, PseudoTypeFalse, PseudoTypeBoolean)
|
||||
}
|
||||
return NewPseudoTypeInferred(node, false)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromObjectLiteral(node *ast.ObjectLiteralExpression) *PseudoType {
|
||||
if errorNodes := ch.canGetTypeFromObjectLiteral(node); errorNodes != nil {
|
||||
return NewPseudoTypeInferredWithErrors(node.AsNode(), false, errorNodes)
|
||||
}
|
||||
// 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 NewPseudoTypeObjectLiteral(nil)
|
||||
}
|
||||
results := make([]*PseudoObjectElement, 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, NewPseudoPropertyAssignment(
|
||||
false,
|
||||
e.Name(),
|
||||
optional,
|
||||
NewPseudoTypeDirect(e.FunctionLikeData().FullSignature),
|
||||
))
|
||||
} else {
|
||||
results = append(results, NewPseudoObjectMethod(
|
||||
e,
|
||||
e.Name(),
|
||||
optional,
|
||||
ch.cloneTypeParameters(e.AsMethodDeclaration().TypeParameters),
|
||||
ch.cloneParameters(e.ParameterList()),
|
||||
ch.createReturnFromSignature(e),
|
||||
))
|
||||
}
|
||||
case ast.KindPropertyAssignment:
|
||||
results = append(results, NewPseudoPropertyAssignment(
|
||||
false,
|
||||
e.Name(),
|
||||
e.AsPropertyAssignment().PostfixToken != nil && e.AsPropertyAssignment().PostfixToken.Kind == ast.KindQuestionToken,
|
||||
ch.typeFromExpression(e.Initializer()),
|
||||
))
|
||||
case ast.KindSetAccessor, ast.KindGetAccessor:
|
||||
member := ch.getAccessorMember(e, e.Name())
|
||||
if member != nil {
|
||||
results = append(results, member)
|
||||
}
|
||||
}
|
||||
}
|
||||
return NewPseudoTypeObjectLiteral(results)
|
||||
}
|
||||
|
||||
// roughly analogous to typeFromObjectLiteralAccessor in strada
|
||||
func (ch *PseudoChecker) getAccessorMember(accessor *ast.Node, name *ast.Node) *PseudoObjectElement {
|
||||
allAccessors := ast.GetAllAccessorDeclarationsForDeclaration(accessor, accessor.Symbol().Declarations) // TODO: node preservation for late-bound accessor pairs?
|
||||
|
||||
// TODO: handle pseudo-annotations from get accessor return positions?
|
||||
if allAccessors.GetAccessor != nil && allAccessors.GetAccessor.Type != nil &&
|
||||
allAccessors.SetAccessor != nil && len(allAccessors.SetAccessor.Parameters.Nodes) > 0 && allAccessors.SetAccessor.Parameters.Nodes[0].AsParameterDeclaration().Type != nil {
|
||||
// We have possible types for both accessors, we can't know if they are the same type so we keep both accessors
|
||||
|
||||
if ast.IsGetAccessorDeclaration(accessor) {
|
||||
return NewPseudoGetAccessor(
|
||||
accessor,
|
||||
name,
|
||||
false,
|
||||
ch.typeFromAccessor(accessor),
|
||||
)
|
||||
} else {
|
||||
return NewPseudoSetAccessor(
|
||||
accessor,
|
||||
name,
|
||||
false,
|
||||
ch.cloneParameters(accessor.AsSetAccessorDeclaration().Parameters)[0],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if accessor == allAccessors.FirstAccessor {
|
||||
// only one annotated accessor; output a property - `readonly` for a single `get` accessor
|
||||
|
||||
accessorType := ch.typeFromAccessor(accessor)
|
||||
readonly := ast.IsGetAccessorDeclaration(accessor) && allAccessors.SecondAccessor == nil
|
||||
return NewPseudoPropertyAssignment(
|
||||
readonly,
|
||||
name,
|
||||
false,
|
||||
accessorType,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// canGetTypeFromObjectLiteral checks whether an object literal can be typed by the pseudochecker.
|
||||
// Returns nil if the object can be typed, or a slice of error nodes (shorthand/spread properties,
|
||||
// non-literal computed names) that prevent typing.
|
||||
func (ch *PseudoChecker) canGetTypeFromObjectLiteral(node *ast.ObjectLiteralExpression) []*ast.Node {
|
||||
if node.Properties == nil || len(node.Properties.Nodes) == 0 {
|
||||
return nil // empty object, ok
|
||||
}
|
||||
var errorNodes []*ast.Node
|
||||
for _, e := range node.Properties.Nodes {
|
||||
if e.Flags&ast.NodeFlagsThisNodeHasError != 0 {
|
||||
errorNodes = append(errorNodes, e)
|
||||
continue
|
||||
}
|
||||
if e.Kind == ast.KindShorthandPropertyAssignment || e.Kind == ast.KindSpreadAssignment {
|
||||
errorNodes = append(errorNodes, e)
|
||||
continue
|
||||
}
|
||||
if e.Name().Flags&ast.NodeFlagsThisNodeHasError != 0 {
|
||||
errorNodes = append(errorNodes, e.Name())
|
||||
continue
|
||||
}
|
||||
if e.Name().Kind == ast.KindPrivateIdentifier {
|
||||
errorNodes = append(errorNodes, e)
|
||||
continue
|
||||
}
|
||||
if e.Name().Kind == ast.KindComputedPropertyName {
|
||||
expression := e.Name().Expression()
|
||||
if !ast.IsPrimitiveLiteralValue(expression, false) {
|
||||
errorNodes = append(errorNodes, e.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorNodes
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromArrayLiteral(node *ast.ArrayLiteralExpression) *PseudoType {
|
||||
if errorNodes := ch.canGetTypeFromArrayLiteral(node); errorNodes != nil {
|
||||
return NewPseudoTypeInferredWithErrors(node.AsNode(), false, errorNodes)
|
||||
}
|
||||
if IsInConstContext(node.AsNode()) && isContextuallyTyped(node.AsNode()) {
|
||||
return NewPseudoTypeInferred(node.AsNode(), false) // expr in an as const cast with a contextual type has variable readonly state, bail
|
||||
}
|
||||
// we are in a const context producing a tuple type, there are no spread elements
|
||||
results := make([]*PseudoType, 0, len(node.Elements.Nodes))
|
||||
for _, e := range node.Elements.Nodes {
|
||||
results = append(results, ch.typeFromExpression(e))
|
||||
}
|
||||
return NewPseudoTypeTuple(results)
|
||||
}
|
||||
|
||||
// canGetTypeFromArrayLiteral checks whether an array literal can be typed by the pseudochecker.
|
||||
// Returns nil if the array can be typed, or a slice of error nodes that prevent typing.
|
||||
// For non-const arrays, the error node is the array expression itself.
|
||||
// For const arrays with spreads, the error node is the spread element.
|
||||
func (ch *PseudoChecker) canGetTypeFromArrayLiteral(node *ast.ArrayLiteralExpression) []*ast.Node {
|
||||
if !IsInConstContext(node.AsNode()) {
|
||||
return []*ast.Node{node.AsNode()}
|
||||
}
|
||||
for _, e := range node.Elements.Nodes {
|
||||
if e.Kind == ast.KindSpreadElement {
|
||||
return []*ast.Node{e}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// See `isConstContext` in `checker.go` - this is basically any node kind mentioned in that
|
||||
func isConstContextPropagatingKind(kind ast.Kind) bool {
|
||||
switch kind {
|
||||
case ast.KindArrayLiteralExpression, ast.KindObjectLiteralExpression,
|
||||
ast.KindParenthesizedExpression, ast.KindSpreadElement, ast.KindPropertyAssignment,
|
||||
ast.KindShorthandPropertyAssignment, ast.KindTemplateSpan, ast.KindPrefixUnaryExpression:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInConstContext 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 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.Parent,
|
||||
func(n *ast.Node) bool {
|
||||
// stop traversing at assertions or anything not an array/object literal, since only those create or transfer const-ness
|
||||
return ast.IsAssertionExpression(n) || !isConstContextPropagatingKind(n.Kind)
|
||||
},
|
||||
)
|
||||
return ast.IsConstAssertion(maybeAssertion)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromPrimitiveLiteralPrefix(node *ast.PrefixUnaryExpression) *PseudoType {
|
||||
expr := node.AsNode()
|
||||
if node.Operator == ast.KindPlusToken {
|
||||
expr = node.Operand
|
||||
}
|
||||
inner := node.Operand
|
||||
if inner.Kind == ast.KindBigIntLiteral {
|
||||
return NewPseudoTypeMaybeConstLocation(node.AsNode(), NewPseudoTypeBigIntLiteral(expr.AsNode()), PseudoTypeBigInt)
|
||||
}
|
||||
if inner.Kind == ast.KindNumericLiteral {
|
||||
return NewPseudoTypeMaybeConstLocation(node.AsNode(), NewPseudoTypeNumericLiteral(expr.AsNode()), PseudoTypeNumber)
|
||||
}
|
||||
debug.FailBadSyntaxKind(inner)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromTypeAssertion(expression *ast.Node, typeNode *ast.Node) *PseudoType {
|
||||
if ast.IsConstTypeReference(typeNode) {
|
||||
return ch.typeFromExpression(expression)
|
||||
}
|
||||
return NewPseudoTypeDirect(typeNode)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromFunctionLikeExpression(node *ast.Node) *PseudoType {
|
||||
if node.FunctionLikeData().FullSignature != nil {
|
||||
return NewPseudoTypeDirect(node.FunctionLikeData().FullSignature)
|
||||
}
|
||||
returnType := ch.createReturnFromSignature(node)
|
||||
typeParameters := ch.cloneTypeParameters(node.FunctionLikeData().TypeParameters)
|
||||
parameters := ch.cloneParameters(node.FunctionLikeData().Parameters)
|
||||
return NewPseudoTypeSingleCallSignature(
|
||||
node,
|
||||
parameters,
|
||||
typeParameters,
|
||||
returnType,
|
||||
)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) 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.AsTypeParameterDeclaration())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func isUndefinedPseudoType(t *PseudoType) bool {
|
||||
return t.Kind == PseudoTypeKindUndefined || (t.Kind == PseudoTypeKindMaybeConstLocation && isUndefinedPseudoType(t.AsPseudoTypeMaybeConstLocation().ConstType))
|
||||
}
|
||||
|
||||
func typeNodeCouldReferToUndefined(node *ast.Node) bool {
|
||||
for node.Kind == ast.KindParenthesizedType {
|
||||
node = node.AsParenthesizedTypeNode().Type
|
||||
}
|
||||
switch node.Kind {
|
||||
// these types require symbolic/type resolution to know if they definitely do or do not refer to `undefined`, so might (or definitely do)
|
||||
case ast.KindTypeReference, ast.KindIndexedAccessType, ast.KindTypeQuery, ast.KindOptionalType, ast.KindRestType, ast.KindImportType:
|
||||
return true
|
||||
case ast.KindIntersectionType:
|
||||
// TODO: why is this not `core.Every`? strada treated unions and intersections the same, but logically every intersection member needs to contain a possible `undefined`
|
||||
// for the result type to contain `undefined`. Likely a bug persisting from strada.
|
||||
return core.Some(node.AsIntersectionTypeNode().Types.Nodes, typeNodeCouldReferToUndefined)
|
||||
case ast.KindUnionType:
|
||||
return core.Some(node.AsUnionTypeNode().Types.Nodes, typeNodeCouldReferToUndefined)
|
||||
case ast.KindConditionalType: // suspect - should be treated as a union of both branches instead, likely a bug persisted from strada
|
||||
return true
|
||||
case ast.KindTypeOperator: // suspect - always refers to a subset of `string | number | symbol` for `keyof` or `symbol` for `unique`
|
||||
return true
|
||||
case ast.KindTypePredicate: // suspect - always refers to `never` or `boolean`, depending on kind - considered possibly-`undefined` referencing for strada compat
|
||||
return true
|
||||
case ast.KindUndefinedKeyword:
|
||||
return true
|
||||
default: // all other keywords, literal types, function-y types, array/tuple types, type literals, template types, this types
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// see this as the inverse of `canAddUndefined` in `expressionToTypeNode` in strada
|
||||
func CouldAlreadyReferToUndefinedType(t *PseudoType) bool {
|
||||
if t.Kind == PseudoTypeKindNoResult || t.Kind == PseudoTypeKindInferred || isUndefinedPseudoType(t) {
|
||||
return true
|
||||
}
|
||||
if t.Kind == PseudoTypeKindMaybeConstLocation {
|
||||
mc := t.AsPseudoTypeMaybeConstLocation()
|
||||
return CouldAlreadyReferToUndefinedType(mc.RegularType) // if we're even asking this question, it's not a `const` location
|
||||
}
|
||||
if t.Kind == PseudoTypeKindDirect {
|
||||
// inspect the direct type node
|
||||
node := t.AsPseudoTypeDirect().TypeNode
|
||||
return typeNodeCouldReferToUndefined(node)
|
||||
}
|
||||
if t.Kind == PseudoTypeKindUnion {
|
||||
return core.Some(t.AsPseudoTypeUnion().Types, CouldAlreadyReferToUndefinedType)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isOptionalInitializedOrRestParameter(node *ast.ParameterDeclarationNode) bool {
|
||||
p := node.AsParameterDeclaration()
|
||||
if p.DotDotDotToken != nil || p.Initializer != nil || p.QuestionToken != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lastRequiredParamIndex returns the index just past the last required parameter
|
||||
// in the list. A parameter is "required" if it has no question token, no initializer,
|
||||
// and no rest token. This is computed in a single reverse pass so callers can
|
||||
// determine "has required parameter after index i" with `i+1 < lastRequired`
|
||||
// (equivalently, `i < lastRequired-1`) in O(1).
|
||||
func lastRequiredParamIndex(params []*ast.Node) int {
|
||||
for i := len(params) - 1; i >= 0; i-- {
|
||||
if !isOptionalInitializedOrRestParameter(params[i]) {
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func addUndefinedIfDefinitelyRequired(expr *PseudoType) *PseudoType {
|
||||
// If `expr` doesn't already contain `| undefined` or a direct/inferred type that may contain `undefined`, add `| undefined`
|
||||
// in Strada, this reached into the checker to see if `undefined` was necessary, using `isRequiredOptionalParameter` from the emit resolver,
|
||||
// but that's not required on top of the syntactic checks to get the same behavior. (If we get the type wrong, it'll mismatch later and be discarded
|
||||
// for an inference error since corsa actually validates that pseudotypes semantically match the inferred type the checker produces)
|
||||
if CouldAlreadyReferToUndefinedType(expr) {
|
||||
return expr // will just error later, more like than not, unless the `undefined` is explicit in the pseudo
|
||||
}
|
||||
// Explicitly add an `| undefined`
|
||||
return NewPseudoTypeUnion([]*PseudoType{expr, PseudoTypeUndefined})
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromParameter(node *ast.ParameterDeclaration) *PseudoType {
|
||||
parent := node.Parent
|
||||
if parent.Kind == ast.KindSetAccessor {
|
||||
return ch.GetTypeOfAccessor(parent)
|
||||
}
|
||||
// Fast path: no initializer means we never need parameter position info.
|
||||
if node.Initializer == nil {
|
||||
if node.Type != nil {
|
||||
return NewPseudoTypeDirect(node.Type)
|
||||
}
|
||||
return NewPseudoTypeNoResult(node.AsNode())
|
||||
}
|
||||
p := parent.Parameters()
|
||||
selfIdx := slices.Index(p, node.AsNode())
|
||||
lastRequired := lastRequiredParamIndex(p)
|
||||
return ch.typeFromParameterWorker(node, selfIdx, lastRequired)
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) typeFromParameterWorker(node *ast.ParameterDeclaration, selfIdx int, lastRequired int) *PseudoType {
|
||||
parent := node.Parent
|
||||
if parent.Kind == ast.KindSetAccessor {
|
||||
return ch.GetTypeOfAccessor(parent)
|
||||
}
|
||||
hasRequiredAfter := selfIdx < lastRequired-1
|
||||
declaredType := node.Type
|
||||
if declaredType != nil {
|
||||
result := NewPseudoTypeDirect(declaredType)
|
||||
// When the parameter has an initializer and strict null checks are enabled,
|
||||
// check if `| undefined` needs to be added because there are required parameters after this one.
|
||||
// This mirrors the checker's getTypeOfParameter which adds optionality for initialized parameters.
|
||||
if ch.strictNullChecks && node.Initializer != nil && hasRequiredAfter {
|
||||
return addUndefinedIfDefinitelyRequired(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
if node.Initializer != nil && ast.IsIdentifier(node.Name()) && !isContextuallyTyped(node.AsNode()) {
|
||||
expr := ch.typeFromExpression(node.Initializer)
|
||||
if expr != nil && (expr.Kind == PseudoTypeKindInferred && len(expr.AsPseudoTypeInferred().ErrorNodes) == 0) {
|
||||
expr = NewPseudoTypeInferredWithErrors(expr.AsPseudoTypeInferred().Expression, false, []*ast.Node{node.AsNode()}) // Move error up to the parameter
|
||||
}
|
||||
if !ch.strictNullChecks {
|
||||
return expr
|
||||
}
|
||||
if !hasRequiredAfter {
|
||||
return expr
|
||||
}
|
||||
// if there is a non-optional parameter after this one, a `| undefined` will need to explicitly be emitted on this parameter, if it's not already there
|
||||
return addUndefinedIfDefinitelyRequired(expr)
|
||||
}
|
||||
// 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 NewPseudoTypeNoResult(node.AsNode())
|
||||
}
|
||||
|
||||
func (ch *PseudoChecker) cloneParameters(nodes *ast.NodeList) []*PseudoParameter {
|
||||
if nodes == nil {
|
||||
return nil
|
||||
}
|
||||
if len(nodes.Nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
lastRequired := lastRequiredParamIndex(nodes.Nodes)
|
||||
result := make([]*PseudoParameter, 0, len(nodes.Nodes))
|
||||
for i, e := range nodes.Nodes {
|
||||
p := e.AsParameterDeclaration()
|
||||
optional := p.QuestionToken != nil
|
||||
if !optional && p.Initializer != nil {
|
||||
// A parameter with an initializer is optional only if all subsequent
|
||||
// parameters are also optional/have initializers/are rest parameters.
|
||||
// This matches the checker's isOptionalParameter semantics.
|
||||
optional = i >= lastRequired-1
|
||||
}
|
||||
result = append(result, NewPseudoParameter(
|
||||
p.DotDotDotToken != nil,
|
||||
e.Name(),
|
||||
optional,
|
||||
ch.typeFromParameterWorker(p, i, lastRequired),
|
||||
))
|
||||
}
|
||||
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.IsSatisfiesExpression(n) {
|
||||
return true
|
||||
}
|
||||
if (ast.IsVariableParameterOrProperty(n) || ast.IsAssertionExpression(n)) && n.Type() != nil && !ast.IsConstAssertion(n) {
|
||||
return true
|
||||
}
|
||||
return ast.IsJsxElement(n) || ast.IsJsxExpression(n)
|
||||
}) != nil
|
||||
}
|
||||
Reference in New Issue
Block a user