Skip to content

Commit 7abf963

Browse files
adds tracing (--generateTrace) (#2914)
Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com>
1 parent e391e41 commit 7abf963

38 files changed

Lines changed: 1973 additions & 37 deletions

internal/api/callbackfs.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ func (fs *callbackFS) WriteFile(path string, data string) error {
200200
return fs.base.WriteFile(path, data)
201201
}
202202

203+
// AppendFile implements vfs.FS - always delegates to base (no callback support).
204+
func (fs *callbackFS) AppendFile(path string, data string) error {
205+
return fs.base.AppendFile(path, data)
206+
}
207+
203208
// Remove implements vfs.FS - always delegates to base (no callback support).
204209
func (fs *callbackFS) Remove(path string) error {
205210
return fs.base.Remove(path)

internal/ast/symbol.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ast
22

33
import (
4+
"strings"
45
"sync/atomic"
56
)
67

@@ -74,3 +75,8 @@ func SymbolName(symbol *Symbol) string {
7475
}
7576
return symbol.Name
7677
}
78+
79+
// EscapeAllInternalSymbolNames replaces internal symbol name markers ("\xFE") with "__".
80+
func EscapeAllInternalSymbolNames(name string) string {
81+
return strings.ReplaceAll(name, InternalSymbolNamePrefix, "__")
82+
}

internal/bundled/embed.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ func (vfs *wrappedFS) WriteFile(path string, data string) error {
159159
return vfs.fs.WriteFile(path, data)
160160
}
161161

162+
func (vfs *wrappedFS) AppendFile(path string, data string) error {
163+
if _, ok := splitPath(path); ok {
164+
panic("cannot write to embedded file system")
165+
}
166+
return vfs.fs.AppendFile(path, data)
167+
}
168+
162169
func (vfs *wrappedFS) Remove(path string) error {
163170
if _, ok := splitPath(path); ok {
164171
panic("cannot remove from embedded file system")

internal/checker/checker.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/microsoft/typescript-go/internal/modulespecifiers"
2626
"github.com/microsoft/typescript-go/internal/scanner"
2727
"github.com/microsoft/typescript-go/internal/stringutil"
28+
"github.com/microsoft/typescript-go/internal/tracing"
2829
"github.com/microsoft/typescript-go/internal/tsoptions"
2930
"github.com/microsoft/typescript-go/internal/tspath"
3031
"github.com/zeebo/xxh3"
@@ -884,14 +885,16 @@ type Checker struct {
884885
nonExistentProperties collections.Set[NonExistentPropertyKey]
885886
deferredDiagnosticCallbacks []func()
886887

887-
mu sync.Mutex
888+
mu sync.Mutex
889+
tracer *Tracer // Optional tracer for trace events and type recording (for --generateTrace)
888890
}
889891

890-
func NewChecker(program Program) (*Checker, *sync.Mutex) {
892+
func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) {
891893
program.BindSourceFiles()
892894

893895
c := &Checker{}
894896
c.id = nextCheckerID.Add(1)
897+
c.tracer = tracer
895898
c.program = program
896899
c.compilerOptions = program.Options()
897900
c.files = program.SourceFiles()
@@ -2139,6 +2142,9 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil
21392142
links := c.sourceFileLinks.Get(sourceFile)
21402143
if !links.typeChecked {
21412144
c.saveDeferredDiagnostics = true
2145+
if tr := c.tracer; tr != nil {
2146+
defer tr.Push(tracing.PhaseCheck, "checkSourceFile", map[string]any{"path": sourceFile.FileName()}, true)()
2147+
}
21422148
// Grammar checking
21432149
c.checkGrammarSourceFile(sourceFile)
21442150
c.renamedBindingElementsInTypes = nil
@@ -2441,6 +2447,9 @@ func (c *Checker) checkDeferredNodes(context *ast.SourceFile) {
24412447
}
24422448

24432449
func (c *Checker) checkDeferredNode(node *ast.Node) {
2450+
if tr := c.tracer; tr != nil {
2451+
defer tr.Push(tracing.PhaseCheck, "checkDeferredNode", map[string]any{"kind": node.Kind, "pos": node.Pos(), "end": node.End(), "path": ast.GetSourceFileOfNode(node).FileName()}, false)()
2452+
}
24442453
saveCurrentNode := c.currentNode
24452454
c.currentNode = node
24462455
c.instantiationCount = 0
@@ -2570,6 +2579,9 @@ func (c *Checker) checkTypeParameterDeferred(node *ast.Node) {
25702579
if ast.IsTypeOrJSTypeAliasDeclaration(node.Parent) && c.getDeclaredTypeOfSymbol(symbol).objectFlags&(ObjectFlagsAnonymous|ObjectFlagsMapped) == 0 {
25712580
c.error(node, diagnostics.Variance_annotations_are_only_supported_in_type_aliases_for_object_function_constructor_and_mapped_types)
25722581
} else if modifiers == ast.ModifierFlagsIn || modifiers == ast.ModifierFlagsOut {
2582+
if tr := c.tracer; tr != nil {
2583+
defer tr.Push(tracing.PhaseCheckTypes, "checkTypeParameterDeferred", map[string]any{"parent": c.getDeclaredTypeOfSymbol(symbol).id, "id": typeParameter.id}, false)()
2584+
}
25732585
source := c.createMarkerType(symbol, typeParameter, core.IfElse(modifiers == ast.ModifierFlagsOut, c.markerSubTypeForCheck, c.markerSuperTypeForCheck))
25742586
target := c.createMarkerType(symbol, typeParameter, core.IfElse(modifiers == ast.ModifierFlagsOut, c.markerSuperTypeForCheck, c.markerSubTypeForCheck))
25752587
saveVarianceTypeParameter := typeParameter
@@ -5614,6 +5626,9 @@ func (c *Checker) checkVariableDeclarationList(node *ast.Node) {
56145626
}
56155627

56165628
func (c *Checker) checkVariableDeclaration(node *ast.Node) {
5629+
if tr := c.tracer; tr != nil {
5630+
defer tr.Push(tracing.PhaseCheck, "checkVariableDeclaration", map[string]any{"kind": node.Kind, "pos": node.Pos(), "end": node.End(), "path": ast.GetSourceFileOfNode(node).FileName()}, false)()
5631+
}
56175632
c.checkGrammarVariableDeclaration(node.AsVariableDeclaration())
56185633
c.checkVariableLikeDeclaration(node)
56195634
}
@@ -7361,6 +7376,9 @@ func (c *Checker) checkExpression(node *ast.Node) *Type {
73617376
}
73627377

73637378
func (c *Checker) checkExpressionEx(node *ast.Node, checkMode CheckMode) *Type {
7379+
if tr := c.tracer; tr != nil {
7380+
defer tr.Push(tracing.PhaseCheck, "checkExpression", map[string]any{"kind": node.Kind, "pos": node.Pos(), "end": node.End(), "path": ast.GetSourceFileOfNode(node).FileName()}, false)()
7381+
}
73647382
saveCurrentNode := c.currentNode
73657383
c.currentNode = node
73667384
c.instantiationCount = 0
@@ -21663,6 +21681,9 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl
2166321681
// We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement
2166421682
// or expression. There is a very high likelihood we're dealing with a combination of infinite generic types
2166521683
// that perpetually generate new type identities, so we stop the recursion here by yielding the error type.
21684+
if tr := c.tracer; tr != nil {
21685+
tr.Instant(tracing.PhaseCheckTypes, "instantiateType_DepthLimit", map[string]any{"typeId": t.id, "instantiationDepth": c.instantiationDepth, "instantiationCount": c.instantiationCount})
21686+
}
2166621687
c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite)
2166721688
return c.errorType
2166821689
}
@@ -24550,6 +24571,9 @@ func (c *Checker) newType(flags TypeFlags, objectFlags ObjectFlags, data TypeDat
2455024571
t.id = TypeId(c.TypeCount)
2455124572
t.checker = c
2455224573
t.data = data
24574+
if c.tracer != nil {
24575+
c.tracer.RecordType(t)
24576+
}
2455324577
return t
2455424578
}
2455524579

@@ -25491,6 +25515,9 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type {
2549125515
// caps union types at 1000 unique object types.
2549225516
estimatedCount := (count / (length - i)) * length
2549325517
if estimatedCount > 1000000 {
25518+
if tr := c.tracer; tr != nil {
25519+
tr.Instant(tracing.PhaseCheckTypes, "removeSubtypes_DepthLimit", map[string]any{"estimatedCount": estimatedCount})
25520+
}
2549425521
c.error(c.currentNode, diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent)
2549525522
return nil
2549625523
}
@@ -26124,6 +26151,9 @@ func compareTypeIds(t1, t2 *Type) int {
2612426151
func (c *Checker) checkCrossProductUnion(types []*Type) bool {
2612526152
size := c.getCrossProductUnionSize(types)
2612626153
if size >= 100_000 {
26154+
if tr := c.tracer; tr != nil {
26155+
tr.Instant(tracing.PhaseCheckTypes, "checkCrossProductUnion_DepthLimit", map[string]any{"size": size})
26156+
}
2612726157
c.error(c.currentNode, diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent)
2612826158
return false
2612926159
}

internal/checker/checker_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,6 @@ func BenchmarkNewChecker(b *testing.B) {
7979
b.ReportAllocs()
8080

8181
for b.Loop() {
82-
checker.NewChecker(p)
82+
checker.NewChecker(p, nil)
8383
}
8484
}

internal/checker/flow.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/microsoft/typescript-go/internal/diagnostics"
1313
"github.com/microsoft/typescript-go/internal/evaluator"
1414
"github.com/microsoft/typescript-go/internal/scanner"
15+
"github.com/microsoft/typescript-go/internal/tracing"
1516
"github.com/zeebo/xxh3"
1617
)
1718

@@ -117,6 +118,9 @@ func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType {
117118
if f.depth == 2000 {
118119
// We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error
119120
// and disable further control flow analysis in the containing function or module body.
121+
if tr := c.tracer; tr != nil {
122+
tr.Instant(tracing.PhaseCheckTypes, "getTypeAtFlowNode_DepthLimit", map[string]any{"depth": f.depth})
123+
}
120124
c.flowAnalysisDisabled = true
121125
c.reportFlowControlError(f.reference)
122126
return FlowType{t: c.errorType}

internal/checker/relater.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/microsoft/typescript-go/internal/debug"
1414
"github.com/microsoft/typescript-go/internal/diagnostics"
1515
"github.com/microsoft/typescript-go/internal/jsnum"
16+
"github.com/microsoft/typescript-go/internal/tracing"
1617
)
1718

1819
type SignatureCheckMode uint32
@@ -373,6 +374,9 @@ func (c *Checker) checkTypeRelatedToEx(
373374
// Record this relation as having failed such that we don't attempt the overflowing operation again.
374375
id, _ := getRelationKey(source, target, IntersectionStateNone, relation == c.identityRelation, false /*ignoreConstraints*/)
375376
relation.set(id, RelationComparisonResultFailed|core.IfElse(r.relationCount <= 0, RelationComparisonResultComplexityOverflow, RelationComparisonResultStackDepthOverflow))
377+
if tr := c.tracer; tr != nil {
378+
tr.Instant(tracing.PhaseCheckTypes, "checkTypeRelatedTo_DepthLimit", map[string]any{"sourceId": source.id, "targetId": target.id, "depth": len(r.sourceStack), "targetDepth": len(r.targetStack)})
379+
}
376380
message := core.IfElse(r.relationCount <= 0, diagnostics.Excessive_complexity_comparing_types_0_and_1, diagnostics.Excessive_stack_depth_comparing_types_0_and_1)
377381
if errorNode == nil {
378382
errorNode = c.currentNode
@@ -1333,6 +1337,19 @@ func (c *Checker) getAliasVariances(symbol *ast.Symbol) []VarianceFlags {
13331337
func (c *Checker) getVariancesWorker(symbol *ast.Symbol, typeParameters []*Type) []VarianceFlags {
13341338
links := c.varianceLinks.Get(symbol)
13351339
if links.variances == nil {
1340+
var traceArgs map[string]any
1341+
if tr := c.tracer; tr != nil {
1342+
traceArgs = map[string]any{"arity": len(typeParameters), "id": c.getDeclaredTypeOfSymbol(symbol).id}
1343+
popFn := tr.Push(tracing.PhaseCheckTypes, "getVariancesWorker", traceArgs, true)
1344+
defer func() {
1345+
formatted := make([]string, len(links.variances))
1346+
for i, v := range links.variances {
1347+
formatted[i] = v.String()
1348+
}
1349+
traceArgs["variances"] = formatted
1350+
popFn()
1351+
}()
1352+
}
13361353
oldVarianceComputation := c.inVarianceComputation
13371354
saveResolutionStart := c.resolutionStart
13381355
if !c.inVarianceComputation {
@@ -2585,6 +2602,7 @@ func (r *Relater) isRelatedToEx(originalSource *Type, originalTarget *Type, recu
25852602
if source.flags&TypeFlagsSingleton != 0 {
25862603
return TernaryTrue
25872604
}
2605+
r.traceUnionsOrIntersectionsTooLarge(source, target)
25882606
return r.recursiveTypeRelatedTo(source, target, false /*reportErrors*/, IntersectionStateNone, recursionFlags)
25892607
}
25902608
// We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common,
@@ -2647,6 +2665,7 @@ func (r *Relater) isRelatedToEx(originalSource *Type, originalTarget *Type, recu
26472665
}
26482666
return TernaryFalse
26492667
}
2668+
r.traceUnionsOrIntersectionsTooLarge(source, target)
26502669
skipCaching := source.flags&TypeFlagsUnion != 0 && len(source.Types()) < 4 && target.flags&TypeFlagsUnion == 0 ||
26512670
target.flags&TypeFlagsUnion != 0 && len(target.Types()) < 4 && source.flags&TypeFlagsStructuredOrInstantiable == 0
26522671
var result Ternary
@@ -3076,8 +3095,14 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError
30763095
r.c.reliabilityFlags = 0
30773096
var result Ternary
30783097
if r.expandingFlags == ExpandingFlagsBoth {
3098+
if tr := r.c.tracer; tr != nil {
3099+
tr.Instant(tracing.PhaseCheckTypes, "recursiveTypeRelatedTo_DepthLimit", map[string]any{"sourceId": source.id, "targetId": target.id, "depth": len(r.sourceStack), "targetDepth": len(r.targetStack)})
3100+
}
30793101
result = TernaryMaybe
30803102
} else {
3103+
if tr := r.c.tracer; tr != nil {
3104+
defer tr.Push(tracing.PhaseCheckTypes, "structuredTypeRelatedTo", map[string]any{"sourceId": source.id, "targetId": target.id}, false)()
3105+
}
30813106
result = r.structuredTypeRelatedTo(source, target, reportErrors, intersectionState)
30823107
}
30833108
propagatingVarianceFlags := r.c.reliabilityFlags
@@ -3955,7 +3980,13 @@ func (r *Relater) typeRelatedToDiscriminatedType(source *Type, target *Type) Ter
39553980
numCombinations := 1
39563981
for _, sourceProperty := range sourcePropertiesFiltered {
39573982
numCombinations *= countTypes(r.c.getNonMissingTypeOfSymbol(sourceProperty))
3958-
if numCombinations == 0 || numCombinations > 25 {
3983+
if numCombinations > 25 {
3984+
if tr := r.c.tracer; tr != nil {
3985+
tr.Instant(tracing.PhaseCheckTypes, "typeRelatedToDiscriminatedType_DepthLimit", map[string]any{"sourceId": source.id, "targetId": target.id, "numCombinations": numCombinations})
3986+
}
3987+
return TernaryFalse
3988+
}
3989+
if numCombinations == 0 {
39593990
return TernaryFalse
39603991
}
39613992
}
@@ -4925,3 +4956,21 @@ func (c *Checker) isTypeDerivedFrom(source *Type, target *Type) bool {
49254956
func (c *Checker) isDistributionDependent(root *ConditionalRoot) bool {
49264957
return root.isDistributive && (c.isTypeParameterPossiblyReferenced(root.checkType, root.node.TrueType) || c.isTypeParameterPossiblyReferenced(root.checkType, root.node.FalseType))
49274958
}
4959+
4960+
func (r *Relater) traceUnionsOrIntersectionsTooLarge(source *Type, target *Type) {
4961+
tr := r.c.tracer
4962+
if tr == nil {
4963+
return
4964+
}
4965+
if source.flags&TypeFlagsUnionOrIntersection != 0 && target.flags&TypeFlagsUnionOrIntersection != 0 {
4966+
if source.objectFlags&target.objectFlags&ObjectFlagsPrimitiveUnion != 0 {
4967+
// There's a fast path for comparing primitive unions
4968+
return
4969+
}
4970+
sourceSize := len(source.Types())
4971+
targetSize := len(target.Types())
4972+
if sourceSize*targetSize > 1_000_000 {
4973+
tr.Instant(tracing.PhaseCheckTypes, "traceUnionsOrIntersectionsTooLarge_DepthLimit", map[string]any{"sourceId": source.id, "sourceSize": sourceSize, "targetId": target.id, "targetSize": targetSize})
4974+
}
4975+
}
4976+
}

0 commit comments

Comments
 (0)