Skip to content
Open
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
592 changes: 512 additions & 80 deletions tsc/internal/checker/checker.go

Large diffs are not rendered by default.

34 changes: 25 additions & 9 deletions tsc/internal/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func (c *Checker) newFlowType(t *Type, incomplete bool) FlowType {
type SharedFlow struct {
flow *ast.FlowNode
flowType FlowType
// loopFrame is the flow loop stack frame that was on top when flowType was
// computed, or zero when the stack was empty.
loopFrame uint64
}

type FlowState struct {
Expand Down Expand Up @@ -79,9 +82,6 @@ func (c *Checker) getFlowTypeOfReference(reference *ast.Node, declaredType *Type
}

func (c *Checker) getFlowTypeOfReferenceEx(reference *ast.Node, declaredType *Type, initialType *Type, flowContainer *ast.Node, flowNode *ast.FlowNode) *Type {
if c.flowAnalysisDisabled {
return c.errorType
}
if flowNode == nil {
flowNode = getFlowNodeOfNode(reference)
if flowNode == nil {
Expand Down Expand Up @@ -117,11 +117,11 @@ func (c *Checker) getFlowTypeOfReferenceEx(reference *ast.Node, declaredType *Ty
func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType {
if f.depth == 2000 {
// We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error
// and disable further control flow analysis in the containing function or module body.
// and give up on this reference. Each reference hits the limit on its own, so its type does not depend
// on which other references were analyzed first.
if tr := c.tracer; tr != nil {
tr.Instant(tracing.PhaseCheckTypes, "getTypeAtFlowNode_DepthLimit", map[string]any{"depth": f.depth})
}
c.flowAnalysisDisabled = true
c.reportFlowControlError(f.reference)
return FlowType{t: c.errorType}
}
Expand All @@ -134,9 +134,13 @@ func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType {
// the same getFlowTypeOfReference invocation. A node is considered shared when it is the
// antecedent of more than one node.
for i := f.sharedFlowStart; i < len(c.sharedFlows); i++ {
if c.sharedFlows[i].flow == flow {
// An incomplete type computed under a loop back edge that has since been
// exited reflects that loop's partial types, so it is no longer valid.
// Reusing it would make the result depend on which reference was
// analyzed first.
if s := &c.sharedFlows[i]; s.flow == flow && (!s.flowType.incomplete || c.isFlowLoopFrameActive(s.loopFrame)) {
f.depth--
return c.sharedFlows[i].flowType
return s.flowType
}
}
sharedFlow = flow
Expand Down Expand Up @@ -198,7 +202,7 @@ func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType {
}
if sharedFlow != nil {
// Record visited node and the associated type in the cache.
c.sharedFlows = append(c.sharedFlows, SharedFlow{flow: sharedFlow, flowType: t})
c.sharedFlows = append(c.sharedFlows, SharedFlow{flow: sharedFlow, flowType: t, loopFrame: c.currentFlowLoopFrame()})
}
f.depth--
return t
Expand Down Expand Up @@ -1322,6 +1326,17 @@ func (c *Checker) getUnionOrEvolvingArrayType(f *FlowState, types []*Type, subty
return result
}

func (c *Checker) currentFlowLoopFrame() uint64 {
if len(c.flowLoopStack) == 0 {
return 0
}
return c.flowLoopStack[len(c.flowLoopStack)-1].id
}

func (c *Checker) isFlowLoopFrameActive(id uint64) bool {
return id == 0 || slices.ContainsFunc(c.flowLoopStack, func(info FlowLoopInfo) bool { return info.id == id })
}

func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowType {
if f.refKey.IsZero() {
f.refKey = c.getFlowReferenceKey(f)
Expand Down Expand Up @@ -1364,7 +1379,8 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT
} else {
// All but the first antecedent are the looping control flow paths that lead
// back to the loop junction. We track these on the flow loop stack.
c.flowLoopStack = append(c.flowLoopStack, FlowLoopInfo{key: key, types: antecedentTypes})
c.flowLoopFrameCount++
c.flowLoopStack = append(c.flowLoopStack, FlowLoopInfo{key: key, types: antecedentTypes, id: c.flowLoopFrameCount})
saveFlowTypeCache := c.flowTypeCache
c.flowTypeCache = nil
flowType = c.getTypeAtFlowNode(f, list.Flow)
Expand Down
6 changes: 4 additions & 2 deletions tsc/internal/checker/nodecopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,10 @@ func getExistingNodeTreeVisitor(b *NodeBuilderImpl, bound *recoveryBoundary) *as
symAtLocation == b.ch.unknownSymbol ||
// If the symbol is not found, but was not found in the original scope either we probably have an error, don't reuse the node
(symAtLocation == nil && sym != nil) ||
// If the symbol is found both in declaration scope and in current scope then it should point to the same reference
(symAtLocation != nil && sym != nil && b.ch.getSymbolIfSameReference(b.ch.getExportSymbolOfValueSymbolIfExported(symAtLocation), sym) == nil) {
// If the symbol is found both in declaration scope and in current scope then it should point to the same reference.
// A parameter in scope may be an instantiation of the one in the declaration, depending on whether
// its type was resolved before its signature was instantiated, so compare what they instantiate.
(symAtLocation != nil && sym != nil && b.ch.getSymbolIfSameReference(b.ch.getTargetSymbol(b.ch.getExportSymbolOfValueSymbolIfExported(symAtLocation)), b.ch.getTargetSymbol(sym)) == nil) {
// In isolated declaration we will not do rest parameter expansion so there is no need to report on these.
if symAtLocation != b.ch.unknownSymbol {
b.ctx.tracker.ReportInferenceFallback(node)
Expand Down
8 changes: 8 additions & 0 deletions tsc/internal/checker/pseudotypenodebuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,14 @@ func (b *NodeBuilderImpl) pseudoTypeToType(t *pseudochecker.PseudoType) *Type {
return b.ch.getReturnTypeOfSignature(b.ch.getSignatureFromDeclaration(node))
}
ty := b.ch.getWidenedType(b.ch.getRegularTypeOfExpression(node))
if ast.IsPropertyAssignment(node.Parent) {
// pseudoTypeToNode serializes the type of the property's symbol, which is
// not widened in the context of its containing object literal.
declared := b.ch.getWidenedLiteralType(b.ch.getTypeOfSymbol(b.ch.getSymbolOfDeclaration(node.Parent)))
if declared != ty {
return nil
}
}
return ty
case pseudochecker.PseudoTypeKindNoResult:
return nil // TODO: extract type selection logic from `serializeTypeForDeclaration`, not needed for current usecases but needed if completeness becomes required
Expand Down
Loading
Loading