Skip to content

Commit 65b5a44

Browse files
committed
Reuse union discrimination when inferring types
1 parent be07e42 commit 65b5a44

13 files changed

Lines changed: 514 additions & 505 deletions

‎tsc/internal/checker/inference.go‎

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -249,26 +249,13 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type)
249249
case target.flags&TypeFlagsUnionOrIntersection != 0:
250250
c.inferToUnionOrIntersectionType(n, source, target)
251251
case source.flags&TypeFlagsUnion != 0:
252-
// Infer from each source union constituent, excluding incompatible fixed discriminants.
253-
discriminants := c.getInferenceDiscriminants(source, target)
254-
inferSources:
255-
for _, sourceType := range source.Types() {
256-
if len(discriminants) != 0 && sourceType.flags&(TypeFlagsObject|TypeFlagsIntersection) != 0 {
257-
for _, targetProp := range discriminants {
258-
sourceProp := c.getPropertyOfType(sourceType, targetProp.Name)
259-
if sourceProp == nil {
260-
continue
261-
}
262-
// Two optional tags always overlap through absence.
263-
if sourceProp.Flags&ast.SymbolFlagsOptional != 0 && targetProp.Flags&ast.SymbolFlagsOptional != 0 {
264-
continue
265-
}
266-
propType := c.getNonMissingTypeOfSymbol(sourceProp)
267-
if isLiteralType(propType) && !c.isTypeAssignableTo(propType, c.getNonMissingTypeOfSymbol(targetProp)) {
268-
continue inferSources
269-
}
270-
}
271-
}
252+
if discriminants := c.getInferenceDiscriminants(source, target); len(discriminants) != 0 {
253+
discriminator := &TypeDiscriminator{c: c, props: discriminants, isRelatedTo: func(targetType *Type, sourceType *Type) Ternary {
254+
return c.compareTypesAssignableSimple(sourceType, targetType)
255+
}}
256+
source = c.discriminateTypeByDiscriminableItems(source, discriminator)
257+
}
258+
for _, sourceType := range source.Distributed() {
272259
c.inferFromTypes(n, sourceType, target)
273260
}
274261
case target.flags&TypeFlagsTemplateLiteral != 0:
@@ -475,18 +462,24 @@ func getTypeListDepth(types []*Type, maxDepth int) int {
475462
}
476463

477464
func (c *Checker) inferToUnionOrIntersectionType(n *InferenceState, source *Type, target *Type) {
478-
var discriminants [][]*ast.Symbol
465+
// Each entry contains the remaining allowed targets for the corresponding source constituent; nil means unrestricted.
466+
var filteredTargetsBySource [][]*Type
479467
if target.flags&TypeFlagsUnion != 0 {
480468
sources := source.Distributed()
481-
discriminants = make([][]*ast.Symbol, len(sources))
469+
filteredTargetsBySource = make([][]*Type, len(sources))
482470
for i, s := range sources {
483-
discriminants[i] = c.getInferenceDiscriminants(target, s)
471+
if discriminants := c.getInferenceDiscriminants(target, s); len(discriminants) != 0 {
472+
discriminator := &TypeDiscriminator{c: c, props: discriminants, isRelatedTo: c.compareTypesAssignableSimple}
473+
if filtered := c.discriminateTypeByDiscriminableItems(target, discriminator); filtered != target {
474+
filteredTargetsBySource[i] = filtered.Distributed()
475+
}
476+
}
484477
}
485478
}
486-
c.inferToMultipleTypes(n, source, target.Types(), target.flags, discriminants)
479+
c.inferToMultipleTypes(n, source, target.Types(), target.flags, filteredTargetsBySource)
487480
}
488481

489-
func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets []*Type, targetFlags TypeFlags, discriminants [][]*ast.Symbol) {
482+
func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets []*Type, targetFlags TypeFlags, filteredTargetsBySource [][]*Type) {
490483
typeVariableCount := 0
491484
if targetFlags&TypeFlagsUnion != 0 {
492485
var nakedTypeVariable *Type
@@ -506,20 +499,21 @@ func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets
506499
if getInferenceInfoForType(n, t) != nil {
507500
nakedTypeVariable = t
508501
typeVariableCount++
502+
// Naked type parameters still consume their entries in the filtered target lists.
503+
for i, remaining := range filteredTargetsBySource {
504+
if len(remaining) != 0 && remaining[0] == t {
505+
filteredTargetsBySource[i] = remaining[1:]
506+
}
507+
}
509508
} else {
510-
inferSources:
511509
for i := range sources {
512-
if len(discriminants) != 0 && len(discriminants[i]) != 0 && t.flags&(TypeFlagsObject|TypeFlagsIntersection) != 0 {
513-
for _, sourceProp := range discriminants[i] {
514-
targetProp := c.getPropertyOfType(t, sourceProp.Name)
515-
if targetProp == nil || sourceProp.Flags&ast.SymbolFlagsOptional != 0 && targetProp.Flags&ast.SymbolFlagsOptional != 0 {
516-
continue
517-
}
518-
propType := c.getNonMissingTypeOfSymbol(targetProp)
519-
if isLiteralType(propType) && !c.isTypeAssignableTo(c.getNonMissingTypeOfSymbol(sourceProp), propType) {
520-
continue inferSources
521-
}
510+
if len(filteredTargetsBySource) != 0 && filteredTargetsBySource[i] != nil {
511+
// Filtering preserves union order, so the next allowed target must be first.
512+
remaining := filteredTargetsBySource[i]
513+
if len(remaining) == 0 || remaining[0] != t {
514+
continue
522515
}
516+
filteredTargetsBySource[i] = remaining[1:]
523517
}
524518
saveInferencePriority := n.inferencePriority
525519
n.inferencePriority = InferencePriorityMaxValue

‎tsc/testdata/baselines/reference/compiler/builtinIterator.types‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ const zero = iterator.filter(isZero);
5050
>isZero : (x: number) => x is 0
5151

5252
const iteratorFromBare = Iterator.from({
53-
>iteratorFromBare : IteratorObject<unknown, undefined, unknown>
54-
>Iterator.from({ next() { return { done: Math.random() < .5, value: "a string", }; },}) : IteratorObject<unknown, undefined, unknown>
53+
>iteratorFromBare : IteratorObject<string, undefined, unknown>
54+
>Iterator.from({ next() { return { done: Math.random() < .5, value: "a string", }; },}) : IteratorObject<string, undefined, unknown>
5555
>Iterator.from : <T>(value: Iterable<T, unknown, undefined> | Iterator<T, unknown, undefined>) => IteratorObject<T, undefined, unknown>
5656
>Iterator : IteratorConstructor
5757
>from : <T>(value: Iterable<T, unknown, undefined> | Iterator<T, unknown, undefined>) => IteratorObject<T, undefined, unknown>

‎tsc/testdata/baselines/reference/compiler/discriminateWithOptionalProperty2(exactoptionalpropertytypes=false).errors.txt‎

Lines changed: 0 additions & 85 deletions
This file was deleted.

‎tsc/testdata/baselines/reference/compiler/discriminateWithOptionalProperty2(exactoptionalpropertytypes=false).types‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,13 @@ const iterable = {
243243
};
244244

245245
const doubles = mapAsyncIterable(iterable, (x) => x + x);
246-
>doubles : AsyncGenerator<unknown, undefined, void>
247-
>mapAsyncIterable(iterable, (x) => x + x) : AsyncGenerator<unknown, undefined, void>
246+
>doubles : AsyncGenerator<number, number, void>
247+
>mapAsyncIterable(iterable, (x) => x + x) : AsyncGenerator<number, number, void>
248248
>mapAsyncIterable : <T, U, R = undefined>(iterable: AsyncGenerator<T, R, undefined> | AsyncIterable<T, R, undefined>, callback: (value: T) => PromiseOrValue<U>) => AsyncGenerator<U, R, void>
249249
>iterable : { [Symbol.asyncIterator](): any; next(): Promise<{ done: boolean; value: number; }>; }
250-
>(x) => x + x : (x: unknown) => any
251-
>x : unknown
252-
>x + x : any
253-
>x : unknown
254-
>x : unknown
250+
>(x) => x + x : (x: number) => number
251+
>x : number
252+
>x + x : number
253+
>x : number
254+
>x : number
255255

‎tsc/testdata/baselines/reference/compiler/discriminateWithOptionalProperty2(exactoptionalpropertytypes=true).errors.txt‎

Lines changed: 0 additions & 85 deletions
This file was deleted.

‎tsc/testdata/baselines/reference/compiler/discriminateWithOptionalProperty2(exactoptionalpropertytypes=true).types‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,13 @@ const iterable = {
243243
};
244244

245245
const doubles = mapAsyncIterable(iterable, (x) => x + x);
246-
>doubles : AsyncGenerator<unknown, undefined, void>
247-
>mapAsyncIterable(iterable, (x) => x + x) : AsyncGenerator<unknown, undefined, void>
246+
>doubles : AsyncGenerator<number, number, void>
247+
>mapAsyncIterable(iterable, (x) => x + x) : AsyncGenerator<number, number, void>
248248
>mapAsyncIterable : <T, U, R = undefined>(iterable: AsyncGenerator<T, R, undefined> | AsyncIterable<T, R, undefined>, callback: (value: T) => PromiseOrValue<U>) => AsyncGenerator<U, R, void>
249249
>iterable : { [Symbol.asyncIterator](): any; next(): Promise<{ done: boolean; value: number; }>; }
250-
>(x) => x + x : (x: unknown) => any
251-
>x : unknown
252-
>x + x : any
253-
>x : unknown
254-
>x : unknown
250+
>(x) => x + x : (x: number) => number
251+
>x : number
252+
>x + x : number
253+
>x : number
254+
>x : number
255255

0 commit comments

Comments
 (0)