Skip to content

Commit 21a3832

Browse files
committed
Rework const context handling to defer psuedotype selection until checker data is ready, reducing literal expression diffs
1 parent c510a1c commit 21a3832

4 files changed

Lines changed: 122 additions & 40 deletions

File tree

internal/checker/nodebuilderimpl.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2018,13 +2018,13 @@ func (b *nodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati
20182018
// !!! expandable hover support
20192019
if tryReuse && declaration != nil && (ast.IsAccessor(declaration) || (ast.HasInferredType(declaration) && !ast.NodeIsSynthesized(declaration) && (t.ObjectFlags()&ObjectFlagsRequiresWidening) == 0)) {
20202020
remove := b.addSymbolTypeToContext(symbol, t)
2021+
var pt *psuedochecker.PsuedoType
20212022
if ast.IsAccessor(declaration) {
2022-
pt := b.pc.GetTypeOfAccessor(declaration)
2023-
result = b.psuedoTypeToNode(pt)
2023+
pt = b.pc.GetTypeOfAccessor(declaration)
20242024
} else {
2025-
pt := b.pc.GetTypeOfDeclaration(declaration)
2026-
result = b.psuedoTypeToNode(pt)
2025+
pt = b.pc.GetTypeOfDeclaration(declaration)
20272026
}
2027+
result = b.psuedoTypeToNode(pt)
20282028
remove()
20292029
}
20302030
if result == nil {

internal/checker/psuedotypenodebuilder.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ func (b *nodeBuilderImpl) psuedoTypeToNode(t *psuedochecker.PsuedoType) *ast.Nod
2323
node := t.Data.(*psuedochecker.PsuedoTypeNoResult).Declaration
2424
b.ctx.tracker.ReportInferenceFallback(node)
2525
return b.serializeTypeForDeclaration(node, nil, nil, false)
26+
case psuedochecker.PsuedoTypeKindMaybeConstLocation:
27+
d := t.Data.(*psuedochecker.PsuedoTypeMaybeConstLocation)
28+
// see checkExpressionWithContextualType for general literal widening rules which need to be emulated here, plus
29+
// checkTemplateLiteralExpression for template literal widening rules if the psuedochecker ever supports literalized templates
30+
isInConstContext := b.ch.isConstContext(d.Node)
31+
if !isInConstContext {
32+
contextualType := b.ch.getContextualType(d.Node, ContextFlagsNone)
33+
t := b.psuedoTypeToType(d.ConstType)
34+
if t != nil && b.ch.isLiteralOfContextualType(t, b.ch.instantiateContextualType(contextualType, d.Node, ContextFlagsNone)) {
35+
isInConstContext = true
36+
}
37+
}
38+
if isInConstContext {
39+
return b.psuedoTypeToNode(d.ConstType)
40+
} else {
41+
return b.psuedoTypeToNode(d.RegularType)
42+
}
2643
case psuedochecker.PsuedoTypeKindUnion:
2744
var res []*ast.Node
2845
var hasElidedType bool
@@ -206,3 +223,72 @@ func (b *nodeBuilderImpl) psuedoParameterToNode(p *psuedochecker.PsuedoParameter
206223
nil,
207224
)
208225
}
226+
227+
func (b *nodeBuilderImpl) psuedoTypeToType(t *psuedochecker.PsuedoType) *Type {
228+
// !!! TODO: only literal types currently mapped because this is only used to determine if literal contextual typing need apply to the psuedotype
229+
// If this is used more broadly, the implementation needs to be filled out more to handle the structural psuedotypes - signatures, objects, tuples, etc
230+
debug.Assert(t != nil, "Attempted to realize nil psuedotype")
231+
switch t.Kind {
232+
case psuedochecker.PsuedoTypeKindDirect:
233+
return b.ch.getTypeFromTypeNode(t.Data.(*psuedochecker.PsuedoTypeDirect).TypeNode)
234+
case psuedochecker.PsuedoTypeKindInferred:
235+
node := t.Data.(*psuedochecker.PsuedoTypeInferred).Expression
236+
ty := b.ch.getTypeOfExpression(node)
237+
return ty
238+
case psuedochecker.PsuedoTypeKindNoResult:
239+
return nil // TODO: extract type selection logic from `serializeTypeForDeclaration`, not needed for current usecases but needed if completeness becomes required
240+
case psuedochecker.PsuedoTypeKindMaybeConstLocation:
241+
d := t.Data.(*psuedochecker.PsuedoTypeMaybeConstLocation)
242+
return b.psuedoTypeToType(d.ConstType) // !!! TODO: not needed for const-checking usecases, but proper context switching behavior required if completeness is required
243+
case psuedochecker.PsuedoTypeKindUnion:
244+
var res []*Type
245+
var hasElidedType bool
246+
members := t.Data.(*psuedochecker.PsuedoTypeUnion).Types
247+
for _, m := range members {
248+
if !b.ch.strictNullChecks {
249+
if m.Kind == psuedochecker.PsuedoTypeKindUndefined || m.Kind == psuedochecker.PsuedoTypeKindNull {
250+
hasElidedType = true
251+
continue
252+
}
253+
}
254+
t := b.psuedoTypeToType(m)
255+
if t == nil {
256+
return nil // propagate failure
257+
}
258+
res = append(res, t)
259+
}
260+
if len(res) == 1 {
261+
return res[0]
262+
}
263+
if len(res) == 0 {
264+
if hasElidedType {
265+
return b.ch.anyType
266+
}
267+
return b.ch.neverType
268+
}
269+
return b.ch.newUnionType(ObjectFlagsNone, res)
270+
case psuedochecker.PsuedoTypeKindUndefined:
271+
return b.ch.undefinedWideningType
272+
case psuedochecker.PsuedoTypeKindNull:
273+
return b.ch.nullWideningType
274+
case psuedochecker.PsuedoTypeKindAny:
275+
return b.ch.anyType
276+
case psuedochecker.PsuedoTypeKindString:
277+
return b.ch.stringType
278+
case psuedochecker.PsuedoTypeKindNumber:
279+
return b.ch.numberType
280+
case psuedochecker.PsuedoTypeKindBigInt:
281+
return b.ch.bigintType
282+
case psuedochecker.PsuedoTypeKindBoolean:
283+
return b.ch.booleanType
284+
case psuedochecker.PsuedoTypeKindFalse:
285+
return b.ch.falseType
286+
case psuedochecker.PsuedoTypeKindTrue:
287+
return b.ch.trueType
288+
case psuedochecker.PsuedoTypeKindStringLiteral, psuedochecker.PsuedoTypeKindNumericLiteral, psuedochecker.PsuedoTypeKindBigIntLiteral:
289+
source := t.Data.(*psuedochecker.PsuedoTypeLiteral).Node
290+
return b.ch.getTypeOfExpression(source) // big shortcut, uses cached expression types where possible
291+
default:
292+
return nil
293+
}
294+
}

internal/psuedochecker/lookup.go

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -267,40 +267,20 @@ func (ch *PsuedoChecker) typeFromExpression(node *ast.Node) *PsuedoType {
267267
case ast.KindClassExpression:
268268
return NewPsuedoTypeInferred(node) // No possible annotation/directly mappable syntax
269269
case ast.KindTemplateExpression:
270-
if ch.isInConstContext(node) {
271-
return NewPsuedoTypeInferred(node) // templateLitWithHoles as const, not supported
272-
}
273-
return PsuedoTypeString
270+
// templateLitWithHoles as const, not supported
271+
return NewPsuedoTypeMaybeConstLocation(node, NewPsuedoTypeInferred(node), PsuedoTypeString)
274272
case ast.KindNumericLiteral:
275-
if ch.isInConstContext(node) {
276-
return NewPsuedoTypeNumericLiteral(node)
277-
}
278-
return PsuedoTypeNumber
273+
return NewPsuedoTypeMaybeConstLocation(node, NewPsuedoTypeNumericLiteral(node), PsuedoTypeNumber)
279274
case ast.KindNoSubstitutionTemplateLiteral:
280-
if ch.isInConstContext(node) {
281-
return NewPsuedoTypeStringLiteral(node)
282-
}
283-
return PsuedoTypeString
275+
return NewPsuedoTypeMaybeConstLocation(node, NewPsuedoTypeStringLiteral(node), PsuedoTypeString)
284276
case ast.KindStringLiteral:
285-
if ch.isInConstContext(node) {
286-
return NewPsuedoTypeStringLiteral(node)
287-
}
288-
return PsuedoTypeString
277+
return NewPsuedoTypeMaybeConstLocation(node, NewPsuedoTypeStringLiteral(node), PsuedoTypeString)
289278
case ast.KindBigIntLiteral:
290-
if ch.isInConstContext(node) {
291-
return NewPsuedoTypeBigIntLiteral(node)
292-
}
293-
return PsuedoTypeBigInt
279+
return NewPsuedoTypeMaybeConstLocation(node, NewPsuedoTypeBigIntLiteral(node), PsuedoTypeBigInt)
294280
case ast.KindTrueKeyword:
295-
if ch.isInConstContext(node) {
296-
return PsuedoTypeTrue
297-
}
298-
return PsuedoTypeBoolean
281+
return NewPsuedoTypeMaybeConstLocation(node, PsuedoTypeTrue, PsuedoTypeBoolean)
299282
case ast.KindFalseKeyword:
300-
if ch.isInConstContext(node) {
301-
return PsuedoTypeFalse
302-
}
303-
return PsuedoTypeBoolean
283+
return NewPsuedoTypeMaybeConstLocation(node, PsuedoTypeFalse, PsuedoTypeBoolean)
304284
}
305285
return NewPsuedoTypeInferred(node)
306286
}
@@ -462,16 +442,10 @@ func (ch *PsuedoChecker) isInConstContext(node *ast.Node) bool {
462442
func (ch *PsuedoChecker) typeFromPrimitiveLiteralPrefix(node *ast.PrefixUnaryExpression) *PsuedoType {
463443
inner := node.Operand
464444
if inner.Kind == ast.KindBigIntLiteral {
465-
if ch.isInConstContext(node.AsNode()) {
466-
return NewPsuedoTypeBigIntLiteral(node.AsNode())
467-
}
468-
return PsuedoTypeBigInt
445+
return NewPsuedoTypeMaybeConstLocation(node.AsNode(), NewPsuedoTypeBigIntLiteral(node.AsNode()), PsuedoTypeBigInt)
469446
}
470447
if inner.Kind == ast.KindNumericLiteral {
471-
if ch.isInConstContext(node.AsNode()) {
472-
return NewPsuedoTypeNumericLiteral(node.AsNode())
473-
}
474-
return PsuedoTypeNumber
448+
return NewPsuedoTypeMaybeConstLocation(node.AsNode(), NewPsuedoTypeNumericLiteral(node.AsNode()), PsuedoTypeNumber)
475449
}
476450
debug.FailBadSyntaxKind(inner)
477451
return nil

internal/psuedochecker/type.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
PsuedoTypeKindDirect PsuedoTypeKind = iota
2323
PsuedoTypeKindInferred
2424
PsuedoTypeKindNoResult
25+
PsuedoTypeKindMaybeConstLocation
2526
PsuedoTypeKindUnion
2627
PsuedoTypeKindUndefined
2728
PsuedoTypeKindNull
@@ -112,6 +113,27 @@ func NewPsuedoTypeNoResult(decl *ast.Node) *PsuedoType {
112113
return NewPsuedoType(PsuedoTypeKindNoResult, &PsuedoTypeNoResult{Declaration: decl})
113114
}
114115

116+
// PsuedoTypeMaybeConstLocation encodes the const/regular types of a location so the builder
117+
// can later select the appropriate psuedotype based on the location's context. This is used
118+
// to ensure accuracy in nested expressions without exposing type-based functionality to the psuedochecker.
119+
// A nodebuilder that doesn't do contextual typing would need to, as policy, reject these types if they
120+
// are in a contextually typed position! (Otherwise they could pick one, but either type could be wrong, depending on context!)
121+
// At the top-level, which is generally what ID is concerned with, nothing is contextually typed, so these cases don't generally
122+
// cause problems. Once you get into reused nodes in nested expressions, however, this becomes important.
123+
// In strada, checker `isConstContext` functionality exposed to the psuedochecker + type comparison sanity checking
124+
// on nested results masks the need for this abstraction, but with it present it clearly highlights a shortcoming
125+
// of the ID infernce model and how "standalone" it can(n't) truly be without substantial restrictions on expression inference.
126+
type PsuedoTypeMaybeConstLocation struct {
127+
PsuedoTypeBase
128+
Node *ast.Node
129+
ConstType *PsuedoType
130+
RegularType *PsuedoType
131+
}
132+
133+
func NewPsuedoTypeMaybeConstLocation(loc *ast.Node, ct *PsuedoType, reg *PsuedoType) *PsuedoType {
134+
return NewPsuedoType(PsuedoTypeKindMaybeConstLocation, &PsuedoTypeMaybeConstLocation{Node: loc, ConstType: ct, RegularType: reg})
135+
}
136+
115137
// PsuedoTypeUnion is a collection of psudotypes joined into a union
116138
type PsuedoTypeUnion struct {
117139
PsuedoTypeBase

0 commit comments

Comments
 (0)