Skip to content

Extract evaluator from checker, more enum emit alignment #607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 44 additions & 43 deletions internal/checker/checker.go

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions internal/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/microsoft/typescript-go/internal/binder"
"github.com/microsoft/typescript-go/internal/compiler/diagnostics"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/evaluator"
"github.com/microsoft/typescript-go/internal/scanner"
)

Expand Down Expand Up @@ -1729,7 +1730,7 @@ func tryGetNameFromType(t *Type) (string, bool) {
case t.flags&TypeFlagsUniqueESSymbol != 0:
return t.AsUniqueESSymbolType().name, true
case t.flags&TypeFlagsStringOrNumberLiteral != 0:
return anyToString(t.AsLiteralType().value), true
return evaluator.AnyToString(t.AsLiteralType().value), true
}
return "", false
}
Expand All @@ -1754,7 +1755,7 @@ func (c *Checker) getDestructuringPropertyName(node *ast.Node) (string, bool) {
func (c *Checker) getLiteralPropertyNameText(name *ast.Node) (string, bool) {
t := c.getLiteralTypeFromPropertyName(name)
if t.flags&(TypeFlagsStringLiteral|TypeFlagsNumberLiteral) != 0 {
return anyToString(t.AsLiteralType().value), true
return evaluator.AnyToString(t.AsLiteralType().value), true
}
return "", false
}
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, ta
case left.flags&TypeFlagsBigInt != 0:
return left
case right.flags&TypeFlagsBigInt != 0:
return c.getBigIntLiteralType(PseudoBigInt{}) // !!!
return c.getBigIntLiteralType(jsnum.PseudoBigInt{}) // !!!
case left.flags&TypeFlagsBigIntLiteral != 0:
return left
case right.flags&TypeFlagsBigIntLiteral != 0 && pseudoBigIntToString(getBigIntLiteralValue(right)) == str:
Expand Down
9 changes: 3 additions & 6 deletions internal/checker/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (p *Printer) printValue(value any) {
p.printNumberLiteral(value)
case bool:
p.printBooleanLiteral(value)
case PseudoBigInt:
case jsnum.PseudoBigInt:
p.printBigIntLiteral(value)
}
}
Expand All @@ -216,11 +216,8 @@ func (p *Printer) printBooleanLiteral(b bool) {
p.print(core.IfElse(b, "true", "false"))
}

func (p *Printer) printBigIntLiteral(b PseudoBigInt) {
if b.negative {
p.print("-")
}
p.print(b.base10Value)
func (p *Printer) printBigIntLiteral(b jsnum.PseudoBigInt) {
p.print(b.String())
}

func (p *Printer) printUniqueESSymbolType(t *Type) {
Expand Down
4 changes: 2 additions & 2 deletions internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ func (c *Checker) isEnumTypeRelatedTo(source *ast.Symbol, target *ast.Symbol, er
c.enumRelation[key] = RelationComparisonResultFailed
return false
}
sourceValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(sourceProperty, ast.KindEnumMember)).value
targetValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(targetProperty, ast.KindEnumMember)).value
sourceValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(sourceProperty, ast.KindEnumMember)).Value
targetValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(targetProperty, ast.KindEnumMember)).Value
if sourceValue != targetValue {
// If we have 2 enums with *known* values that differ, they are incompatible.
if sourceValue != nil && targetValue != nil {
Expand Down
8 changes: 2 additions & 6 deletions internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/evaluator"
)

//go:generate go tool golang.org/x/tools/cmd/stringer -type=SignatureKind -output=stringer_generated.go
Expand Down Expand Up @@ -328,7 +329,7 @@ type TypeNodeLinks struct {
// Links for enum members

type EnumMemberLinks struct {
value EvaluatorResult // Constant value of enum member
value evaluator.Result // Constant value of enum member
}

// Links for assertion expressions
Expand Down Expand Up @@ -715,11 +716,6 @@ type LiteralType struct {
regularType *Type // Regular version of type
}

type PseudoBigInt struct {
negative bool
base10Value string
}

// UniqueESSymbolTypeData

type UniqueESSymbolType struct {
Expand Down
152 changes: 2 additions & 150 deletions internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1208,21 +1208,6 @@ func isThisProperty(node *ast.Node) bool {
return (ast.IsPropertyAccessExpression(node) || ast.IsElementAccessExpression(node)) && node.Expression().Kind == ast.KindThisKeyword
}

func anyToString(v any) string {
// !!! This function should behave identically to the expression `"" + v` in JS
switch v := v.(type) {
case string:
return v
case jsnum.Number:
return v.String()
case bool:
return core.IfElse(v, "true", "false")
case PseudoBigInt:
return "(BigInt)" // !!!
}
panic("Unhandled case in anyToString")
}

func isValidNumberString(s string, roundTripOnly bool) bool {
if s == "" {
return false
Expand Down Expand Up @@ -1421,136 +1406,6 @@ func isObjectLiteralElementLike(node *ast.Node) bool {
return ast.IsObjectLiteralElement(node)
}

type EvaluatorResult struct {
value any
isSyntacticallyString bool
resolvedOtherFiles bool
hasExternalReferences bool
}

func evaluatorResult(value any, isSyntacticallyString bool, resolvedOtherFiles bool, hasExternalReferences bool) EvaluatorResult {
return EvaluatorResult{value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences}
}

type Evaluator func(expr *ast.Node, location *ast.Node) EvaluatorResult

func createEvaluator(evaluateEntity Evaluator) Evaluator {
var evaluate Evaluator
evaluateTemplateExpression := func(expr *ast.Node, location *ast.Node) EvaluatorResult {
var sb strings.Builder
sb.WriteString(expr.AsTemplateExpression().Head.Text())
resolvedOtherFiles := false
hasExternalReferences := false
for _, span := range expr.AsTemplateExpression().TemplateSpans.Nodes {
spanResult := evaluate(span.Expression(), location)
if spanResult.value == nil {
return evaluatorResult(nil, true /*isSyntacticallyString*/, false, false)
}
sb.WriteString(anyToString(spanResult.value))
sb.WriteString(span.AsTemplateSpan().Literal.Text())
resolvedOtherFiles = resolvedOtherFiles || spanResult.resolvedOtherFiles
hasExternalReferences = hasExternalReferences || spanResult.hasExternalReferences
}
return evaluatorResult(sb.String(), true, resolvedOtherFiles, hasExternalReferences)
}
evaluate = func(expr *ast.Node, location *ast.Node) EvaluatorResult {
isSyntacticallyString := false
resolvedOtherFiles := false
hasExternalReferences := false
// It's unclear when/whether we should consider skipping other kinds of outer expressions.
// Type assertions intentionally break evaluation when evaluating literal types, such as:
// type T = `one ${"two" as any} three`; // string
// But it's less clear whether such an assertion should break enum member evaluation:
// enum E {
// A = "one" as any
// }
// SatisfiesExpressions and non-null assertions seem to have even less reason to break
// emitting enum members as literals. However, these expressions also break Babel's
// evaluation (but not esbuild's), and the isolatedModules errors we give depend on
// our evaluation results, so we're currently being conservative so as to issue errors
// on code that might break Babel.
expr = ast.SkipParentheses(expr)
switch expr.Kind {
case ast.KindPrefixUnaryExpression:
result := evaluate(expr.AsPrefixUnaryExpression().Operand, location)
resolvedOtherFiles = result.resolvedOtherFiles
hasExternalReferences = result.hasExternalReferences
if value, ok := result.value.(jsnum.Number); ok {
switch expr.AsPrefixUnaryExpression().Operator {
case ast.KindPlusToken:
return evaluatorResult(value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindMinusToken:
return evaluatorResult(-value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindTildeToken:
return evaluatorResult(value.BitwiseNOT(), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
}
}
case ast.KindBinaryExpression:
left := evaluate(expr.AsBinaryExpression().Left, location)
right := evaluate(expr.AsBinaryExpression().Right, location)
operator := expr.AsBinaryExpression().OperatorToken.Kind
isSyntacticallyString = (left.isSyntacticallyString || right.isSyntacticallyString) && expr.AsBinaryExpression().OperatorToken.Kind == ast.KindPlusToken
resolvedOtherFiles = left.resolvedOtherFiles || right.resolvedOtherFiles
hasExternalReferences = left.hasExternalReferences || right.hasExternalReferences
leftNum, leftIsNum := left.value.(jsnum.Number)
rightNum, rightIsNum := right.value.(jsnum.Number)
if leftIsNum && rightIsNum {
switch operator {
case ast.KindBarToken:
return evaluatorResult(leftNum.BitwiseOR(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindAmpersandToken:
return evaluatorResult(leftNum.BitwiseAND(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindGreaterThanGreaterThanToken:
return evaluatorResult(leftNum.SignedRightShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindGreaterThanGreaterThanGreaterThanToken:
return evaluatorResult(leftNum.UnsignedRightShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindLessThanLessThanToken:
return evaluatorResult(leftNum.LeftShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindCaretToken:
return evaluatorResult(leftNum.BitwiseXOR(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindAsteriskToken:
return evaluatorResult(leftNum*rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindSlashToken:
return evaluatorResult(leftNum/rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindPlusToken:
return evaluatorResult(leftNum+rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindMinusToken:
return evaluatorResult(leftNum-rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindPercentToken:
return evaluatorResult(leftNum.Remainder(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindAsteriskAsteriskToken:
return evaluatorResult(leftNum.Exponentiate(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
}
}
leftStr, leftIsStr := left.value.(string)
rightStr, rightIsStr := right.value.(string)
if (leftIsStr || leftIsNum) && (rightIsStr || rightIsNum) && operator == ast.KindPlusToken {
if leftIsNum {
leftStr = leftNum.String()
}
if rightIsNum {
rightStr = rightNum.String()
}
return evaluatorResult(leftStr+rightStr, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
}
case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral:
return evaluatorResult(expr.Text(), true /*isSyntacticallyString*/, false, false)
case ast.KindTemplateExpression:
return evaluateTemplateExpression(expr, location)
case ast.KindNumericLiteral:
return evaluatorResult(jsnum.FromString(expr.Text()), false, false, false)
case ast.KindIdentifier, ast.KindElementAccessExpression:
return evaluateEntity(expr, location)
case ast.KindPropertyAccessExpression:
if ast.IsEntityNameExpression(expr) {
return evaluateEntity(expr, location)
}
}
return evaluatorResult(nil, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
}
return evaluate
}

func isInfinityOrNaNString(name string) bool {
return name == "Infinity" || name == "-Infinity" || name == "NaN"
}
Expand Down Expand Up @@ -1842,11 +1697,8 @@ func expressionResultIsUnused(node *ast.Node) bool {
}
}

func pseudoBigIntToString(value PseudoBigInt) string {
if value.negative && value.base10Value != "0" {
return "-" + value.base10Value
}
return value.base10Value
func pseudoBigIntToString(value jsnum.PseudoBigInt) string {
return value.String()
}

func getSuperContainer(node *ast.Node, stopOnFunctions bool) *ast.Node {
Expand Down
Loading