vendor tsgo
This commit is contained in:
21
tools/tsgo/internal/pseudochecker/checker.go
Normal file
21
tools/tsgo/internal/pseudochecker/checker.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// pseudochecker is a limited "checker" that returns pseudo-"types" of expressions - mostly those which trivially have type nodes
|
||||
package pseudochecker
|
||||
|
||||
// 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 PseudoChecker struct {
|
||||
strictNullChecks bool
|
||||
exactOptionalPropertyTypes bool
|
||||
}
|
||||
|
||||
func NewPseudoChecker(strictNullChecks bool, exactOptionalPropertyTypes bool) *PseudoChecker {
|
||||
return &PseudoChecker{strictNullChecks: strictNullChecks, exactOptionalPropertyTypes: exactOptionalPropertyTypes}
|
||||
}
|
||||
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
|
||||
}
|
||||
376
tools/tsgo/internal/pseudochecker/type.go
Normal file
376
tools/tsgo/internal/pseudochecker/type.go
Normal file
@@ -0,0 +1,376 @@
|
||||
package pseudochecker
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
)
|
||||
|
||||
// `PseudoType`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 pseudo-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 `PseudoType` into a `Node`, and can cache
|
||||
// `PseudoType`s across multiple target positions.
|
||||
|
||||
type PseudoTypeKind int16
|
||||
|
||||
const (
|
||||
PseudoTypeKindDirect PseudoTypeKind = iota
|
||||
PseudoTypeKindInferred
|
||||
PseudoTypeKindNoResult
|
||||
PseudoTypeKindMaybeConstLocation
|
||||
PseudoTypeKindUnion
|
||||
PseudoTypeKindUndefined
|
||||
PseudoTypeKindNull
|
||||
PseudoTypeKindAny
|
||||
PseudoTypeKindString
|
||||
PseudoTypeKindNumber
|
||||
PseudoTypeKindBigInt
|
||||
PseudoTypeKindBoolean
|
||||
PseudoTypeKindFalse
|
||||
PseudoTypeKindTrue
|
||||
PseudoTypeKindSingleCallSignature
|
||||
PseudoTypeKindTuple
|
||||
PseudoTypeKindObjectLiteral
|
||||
PseudoTypeKindStringLiteral
|
||||
PseudoTypeKindNumericLiteral
|
||||
PseudoTypeKindBigIntLiteral
|
||||
)
|
||||
|
||||
type PseudoType struct {
|
||||
Kind PseudoTypeKind
|
||||
data pseudoTypeData
|
||||
}
|
||||
|
||||
func newPseudoType(kind PseudoTypeKind, data pseudoTypeData) *PseudoType {
|
||||
n := data.AsPseudoType()
|
||||
n.Kind = kind
|
||||
n.data = data
|
||||
return n
|
||||
}
|
||||
|
||||
type pseudoTypeData interface {
|
||||
AsPseudoType() *PseudoType
|
||||
}
|
||||
|
||||
type PseudoTypeDefault struct {
|
||||
PseudoType
|
||||
}
|
||||
|
||||
func (b *PseudoTypeDefault) AsPseudoType() *PseudoType { return &b.PseudoType }
|
||||
|
||||
type PseudoTypeBase struct {
|
||||
PseudoTypeDefault
|
||||
}
|
||||
|
||||
var (
|
||||
PseudoTypeUndefined = newPseudoType(PseudoTypeKindUndefined, &PseudoTypeBase{})
|
||||
PseudoTypeNull = newPseudoType(PseudoTypeKindNull, &PseudoTypeBase{})
|
||||
PseudoTypeAny = newPseudoType(PseudoTypeKindAny, &PseudoTypeBase{})
|
||||
PseudoTypeString = newPseudoType(PseudoTypeKindString, &PseudoTypeBase{})
|
||||
PseudoTypeNumber = newPseudoType(PseudoTypeKindNumber, &PseudoTypeBase{})
|
||||
PseudoTypeBigInt = newPseudoType(PseudoTypeKindBigInt, &PseudoTypeBase{})
|
||||
PseudoTypeBoolean = newPseudoType(PseudoTypeKindBoolean, &PseudoTypeBase{})
|
||||
PseudoTypeFalse = newPseudoType(PseudoTypeKindFalse, &PseudoTypeBase{})
|
||||
PseudoTypeTrue = newPseudoType(PseudoTypeKindTrue, &PseudoTypeBase{})
|
||||
)
|
||||
|
||||
// PseudoTypeDirect directly encodes the type referred to by a given TypeNode
|
||||
type PseudoTypeDirect struct {
|
||||
PseudoTypeBase
|
||||
TypeNode *ast.Node
|
||||
}
|
||||
|
||||
func NewPseudoTypeDirect(typeNode *ast.Node) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindDirect, &PseudoTypeDirect{TypeNode: typeNode})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeDirect() *PseudoTypeDirect { return t.data.(*PseudoTypeDirect) }
|
||||
|
||||
// PseudoTypeInferred directly encodes the type referred to by a given Expression
|
||||
// These represent cases where the expression was too complex for the pseudochecker.
|
||||
// Most of the time, these locations will produce an error under ID.
|
||||
// Specific error nodes (shorthand properties, spread assignments, etc.) are stored on the
|
||||
// ErrorNodes field, collected during pseudochecker construction.
|
||||
type PseudoTypeInferred struct {
|
||||
PseudoTypeBase
|
||||
Expression *ast.Node
|
||||
ErrorNodes []*ast.Node
|
||||
IsSignatureReturn bool
|
||||
}
|
||||
|
||||
func NewPseudoTypeInferred(expr *ast.Node, isSignatureReturn bool) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindInferred, &PseudoTypeInferred{Expression: expr, IsSignatureReturn: isSignatureReturn})
|
||||
}
|
||||
|
||||
func NewPseudoTypeInferredWithErrors(expr *ast.Node, isSignatureReturn bool, errorNodes []*ast.Node) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindInferred, &PseudoTypeInferred{Expression: expr, ErrorNodes: errorNodes, IsSignatureReturn: isSignatureReturn})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeInferred() *PseudoTypeInferred { return t.data.(*PseudoTypeInferred) }
|
||||
|
||||
// PseudoTypeNoResult is analogous to PseudoTypeInferred in that it references a case
|
||||
// where the type was too complex for the pseudochecker. Rather than an expression, however,
|
||||
// it is referring to the return type of a signature or declaration.
|
||||
type PseudoTypeNoResult struct {
|
||||
PseudoTypeBase
|
||||
Declaration *ast.Node
|
||||
}
|
||||
|
||||
func NewPseudoTypeNoResult(decl *ast.Node) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindNoResult, &PseudoTypeNoResult{Declaration: decl})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeNoResult() *PseudoTypeNoResult { return t.data.(*PseudoTypeNoResult) }
|
||||
|
||||
// PseudoTypeMaybeConstLocation encodes the const/regular types of a location so the builder
|
||||
// can later select the appropriate pseudotype based on the location's context. This is used
|
||||
// to ensure accuracy in nested expressions without exposing type-based functionality to the pseudochecker.
|
||||
// A nodebuilder that doesn't do contextual typing would need to, as policy, reject these types if they
|
||||
// are in a contextually typed position! (Otherwise they could pick one, but either type could be wrong, depending on context!)
|
||||
// At the top-level, which is generally what ID is concerned with, nothing is contextually typed, so these cases don't generally
|
||||
// cause problems. Once you get into reused nodes in nested expressions, however, this becomes important.
|
||||
// In strada, checker `isConstContext` functionality exposed to the pseudochecker + type comparison sanity checking
|
||||
// on nested results masks the need for this abstraction, but with it present it clearly highlights a shortcoming
|
||||
// of the ID infernce model and how "standalone" it can(n't) truly be without substantial restrictions on expression inference.
|
||||
type PseudoTypeMaybeConstLocation struct {
|
||||
PseudoTypeBase
|
||||
Node *ast.Node
|
||||
ConstType *PseudoType
|
||||
RegularType *PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoTypeMaybeConstLocation(loc *ast.Node, ct *PseudoType, reg *PseudoType) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindMaybeConstLocation, &PseudoTypeMaybeConstLocation{Node: loc, ConstType: ct, RegularType: reg})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeMaybeConstLocation() *PseudoTypeMaybeConstLocation {
|
||||
return t.data.(*PseudoTypeMaybeConstLocation)
|
||||
}
|
||||
|
||||
// PseudoTypeUnion is a collection of psudotypes joined into a union
|
||||
type PseudoTypeUnion struct {
|
||||
PseudoTypeBase
|
||||
Types []*PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoTypeUnion(types []*PseudoType) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindUnion, &PseudoTypeUnion{Types: types})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeUnion() *PseudoTypeUnion {
|
||||
return t.data.(*PseudoTypeUnion)
|
||||
}
|
||||
|
||||
type PseudoParameter struct {
|
||||
Rest bool
|
||||
Name *ast.Node
|
||||
Optional bool
|
||||
Type *PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoParameter(isRest bool, name *ast.Node, isOptional bool, t *PseudoType) *PseudoParameter {
|
||||
return &PseudoParameter{Rest: isRest, Name: name, Optional: isOptional, Type: t}
|
||||
}
|
||||
|
||||
// PseudoTypeSingleCallSignature represents an object type with a single call signature, like an arrow or function expression
|
||||
type PseudoTypeSingleCallSignature struct {
|
||||
PseudoTypeBase
|
||||
Signature *ast.Node
|
||||
Parameters []*PseudoParameter
|
||||
TypeParameters []*ast.TypeParameterDeclaration
|
||||
ReturnType *PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoTypeSingleCallSignature(signature *ast.Node, parameters []*PseudoParameter, typeParameters []*ast.TypeParameterDeclaration, returnType *PseudoType) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindSingleCallSignature, &PseudoTypeSingleCallSignature{
|
||||
Signature: signature,
|
||||
Parameters: parameters,
|
||||
TypeParameters: typeParameters,
|
||||
ReturnType: returnType,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeSingleCallSignature() *PseudoTypeSingleCallSignature {
|
||||
return t.data.(*PseudoTypeSingleCallSignature)
|
||||
}
|
||||
|
||||
// PseudoTypeTuple represents a tuple originaing from an `as const` array literal
|
||||
type PseudoTypeTuple struct {
|
||||
PseudoTypeBase
|
||||
Elements []*PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoTypeTuple(elements []*PseudoType) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindTuple, &PseudoTypeTuple{
|
||||
Elements: elements,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeTuple() *PseudoTypeTuple {
|
||||
return t.data.(*PseudoTypeTuple)
|
||||
}
|
||||
|
||||
type PseudoObjectElement struct {
|
||||
Name *ast.Node
|
||||
Optional bool
|
||||
Kind PseudoObjectElementKind
|
||||
data pseudoObjectElementData
|
||||
}
|
||||
|
||||
func (e *PseudoObjectElement) AsPseudoObjectElement() *PseudoObjectElement { return e }
|
||||
|
||||
func (e *PseudoObjectElement) Signature() *ast.Node {
|
||||
switch e.Kind {
|
||||
case PseudoObjectElementKindMethod:
|
||||
return e.AsPseudoObjectMethod().Signature
|
||||
case PseudoObjectElementKindSetAccessor:
|
||||
return e.AsPseudoSetAccessor().Signature
|
||||
case PseudoObjectElementKindGetAccessor:
|
||||
return e.AsPseudoGetAccessor().Signature
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type PseudoObjectElementKind int8
|
||||
|
||||
const (
|
||||
PseudoObjectElementKindMethod PseudoObjectElementKind = iota
|
||||
PseudoObjectElementKindPropertyAssignment
|
||||
PseudoObjectElementKindSetAccessor
|
||||
PseudoObjectElementKindGetAccessor
|
||||
)
|
||||
|
||||
type pseudoObjectElementData interface {
|
||||
AsPseudoObjectElement() *PseudoObjectElement
|
||||
}
|
||||
|
||||
func newPseudoObjectElement(kind PseudoObjectElementKind, name *ast.Node, optional bool, data pseudoObjectElementData) *PseudoObjectElement {
|
||||
e := data.AsPseudoObjectElement()
|
||||
e.Kind = kind
|
||||
e.Name = name
|
||||
e.Optional = optional
|
||||
e.data = data
|
||||
return e
|
||||
}
|
||||
|
||||
type PseudoObjectMethod struct {
|
||||
PseudoObjectElement
|
||||
Signature *ast.Node
|
||||
TypeParameters []*ast.TypeParameterDeclaration
|
||||
Parameters []*PseudoParameter
|
||||
ReturnType *PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoObjectMethod(signature *ast.Node, name *ast.Node, optional bool, typeParameters []*ast.TypeParameterDeclaration, parameters []*PseudoParameter, returnType *PseudoType) *PseudoObjectElement {
|
||||
return newPseudoObjectElement(PseudoObjectElementKindMethod, name, optional, &PseudoObjectMethod{
|
||||
Signature: signature,
|
||||
TypeParameters: typeParameters,
|
||||
Parameters: parameters,
|
||||
ReturnType: returnType,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *PseudoObjectElement) AsPseudoObjectMethod() *PseudoObjectMethod {
|
||||
return e.data.(*PseudoObjectMethod)
|
||||
}
|
||||
|
||||
type PseudoPropertyAssignment struct {
|
||||
PseudoObjectElement
|
||||
Readonly bool
|
||||
Type *PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoPropertyAssignment(readonly bool, name *ast.Node, optional bool, t *PseudoType) *PseudoObjectElement {
|
||||
return newPseudoObjectElement(PseudoObjectElementKindPropertyAssignment, name, optional, &PseudoPropertyAssignment{
|
||||
Readonly: readonly,
|
||||
Type: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *PseudoObjectElement) AsPseudoPropertyAssignment() *PseudoPropertyAssignment {
|
||||
return e.data.(*PseudoPropertyAssignment)
|
||||
}
|
||||
|
||||
type PseudoSetAccessor struct {
|
||||
PseudoObjectElement
|
||||
Signature *ast.Node
|
||||
Parameter *PseudoParameter
|
||||
}
|
||||
|
||||
func NewPseudoSetAccessor(signature *ast.Node, name *ast.Node, optional bool, p *PseudoParameter) *PseudoObjectElement {
|
||||
return newPseudoObjectElement(PseudoObjectElementKindSetAccessor, name, optional, &PseudoSetAccessor{
|
||||
Signature: signature,
|
||||
Parameter: p,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *PseudoObjectElement) AsPseudoSetAccessor() *PseudoSetAccessor {
|
||||
return e.data.(*PseudoSetAccessor)
|
||||
}
|
||||
|
||||
type PseudoGetAccessor struct {
|
||||
PseudoObjectElement
|
||||
Signature *ast.Node
|
||||
Type *PseudoType
|
||||
}
|
||||
|
||||
func NewPseudoGetAccessor(signature *ast.Node, name *ast.Node, optional bool, t *PseudoType) *PseudoObjectElement {
|
||||
return newPseudoObjectElement(PseudoObjectElementKindGetAccessor, name, optional, &PseudoGetAccessor{
|
||||
Signature: signature,
|
||||
Type: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *PseudoObjectElement) AsPseudoGetAccessor() *PseudoGetAccessor {
|
||||
return e.data.(*PseudoGetAccessor)
|
||||
}
|
||||
|
||||
// PseudoTypeObjectLiteral represents an object type originaing from an object literal
|
||||
type PseudoTypeObjectLiteral struct {
|
||||
PseudoTypeBase
|
||||
Elements []*PseudoObjectElement
|
||||
}
|
||||
|
||||
func NewPseudoTypeObjectLiteral(elements []*PseudoObjectElement) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindObjectLiteral, &PseudoTypeObjectLiteral{
|
||||
Elements: elements,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeObjectLiteral() *PseudoTypeObjectLiteral {
|
||||
return t.data.(*PseudoTypeObjectLiteral)
|
||||
}
|
||||
|
||||
// PseudoTypeLiteral represents a literal type
|
||||
type PseudoTypeLiteral struct {
|
||||
PseudoTypeBase
|
||||
Node *ast.Node
|
||||
}
|
||||
|
||||
func NewPseudoTypeStringLiteral(node *ast.Node) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindStringLiteral, &PseudoTypeLiteral{
|
||||
Node: node,
|
||||
})
|
||||
}
|
||||
|
||||
func NewPseudoTypeNumericLiteral(node *ast.Node) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindNumericLiteral, &PseudoTypeLiteral{
|
||||
Node: node,
|
||||
})
|
||||
}
|
||||
|
||||
func NewPseudoTypeBigIntLiteral(node *ast.Node) *PseudoType {
|
||||
return newPseudoType(PseudoTypeKindBigIntLiteral, &PseudoTypeLiteral{
|
||||
Node: node,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *PseudoType) AsPseudoTypeLiteral() *PseudoTypeLiteral {
|
||||
return t.data.(*PseudoTypeLiteral)
|
||||
}
|
||||
Reference in New Issue
Block a user