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
2 changes: 1 addition & 1 deletion tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -31371,7 +31371,7 @@ func (d *ObjectLiteralDiscriminator) name(index int) string {
return d.members[index-len(d.props)].Name()
}

func (d *ObjectLiteralDiscriminator) matches(index int, t *Type) bool {
func (d *ObjectLiteralDiscriminator) matches(index int, t *Type, _ bool) bool {
var propType *Type
if index < len(d.props) {
prop := d.props[index]
Expand Down
59 changes: 54 additions & 5 deletions tsc/internal/checker/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,16 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type)
case target.flags&TypeFlagsConditional != 0:
c.invokeOnce(n, source, target, (*Checker).inferToConditionalType)
case target.flags&TypeFlagsUnionOrIntersection != 0:
c.inferToMultipleTypes(n, source, target.Types(), target.flags)
c.inferToUnionOrIntersectionType(n, source, target)
case source.flags&TypeFlagsUnion != 0:
// Source is a union or intersection type, infer from each constituent type
for _, sourceType := range source.Types() {
sources := source.Types()
if discriminants := c.getInferenceDiscriminants(source, target); len(discriminants) != 0 {
discriminator := &TypeDiscriminator{c: c, props: discriminants, reverse: true, isRelatedTo: c.compareTypesComparableSimple}
if filtered := c.discriminateTypesByDiscriminableItems(source, discriminator); filtered != nil {
sources = filtered
}
}
for _, sourceType := range sources {
c.inferFromTypes(n, sourceType, target)
}
case target.flags&TypeFlagsTemplateLiteral != 0:
Expand Down Expand Up @@ -456,7 +462,26 @@ func getTypeListDepth(types []*Type, maxDepth int) int {
return depth
}

func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets []*Type, targetFlags TypeFlags) {
func (c *Checker) inferToUnionOrIntersectionType(n *InferenceState, source *Type, target *Type) {
// Each entry contains the remaining allowed targets for the corresponding source constituent.
// A nil entry means unrestricted.
var filteredTargetsBySource [][]*Type
if target.flags&TypeFlagsUnion != 0 {
sources := source.Distributed()
filteredTargetsBySource = make([][]*Type, len(sources))
for i, s := range sources {
if discriminants := c.getInferenceDiscriminants(target, s); len(discriminants) != 0 {
discriminator := &TypeDiscriminator{c: c, props: discriminants, isRelatedTo: c.compareTypesComparableSimple}
if filtered := c.discriminateTypesByDiscriminableItems(target, discriminator); filtered != nil {
filteredTargetsBySource[i] = filtered
}
}
}
}
c.inferToMultipleTypes(n, source, target.Types(), target.flags, filteredTargetsBySource)
}

func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets []*Type, targetFlags TypeFlags, filteredTargetsBySource [][]*Type) {
typeVariableCount := 0
if targetFlags&TypeFlagsUnion != 0 {
var nakedTypeVariable *Type
Expand All @@ -476,8 +501,22 @@ func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets
if getInferenceInfoForType(n, t) != nil {
nakedTypeVariable = t
typeVariableCount++
// Naked type parameters still consume their entries in the filtered target lists.
for i, remaining := range filteredTargetsBySource {
if len(remaining) != 0 && remaining[0] == t {
filteredTargetsBySource[i] = remaining[1:]
}
}
} else {
for i := range sources {
if len(filteredTargetsBySource) != 0 && filteredTargetsBySource[i] != nil {
// Filtering preserves union order, so the next allowed target must be first.
remaining := filteredTargetsBySource[i]
if len(remaining) == 0 || remaining[0] != t {
continue
}
filteredTargetsBySource[i] = remaining[1:]
}
saveInferencePriority := n.inferencePriority
n.inferencePriority = InferencePriorityMaxValue
c.inferFromTypes(n, sources[i], t)
Expand Down Expand Up @@ -558,7 +597,7 @@ func getSingleTypeVariableFromIntersectionTypes(n *InferenceState, types []*Type
func (c *Checker) inferToMultipleTypesWithPriority(n *InferenceState, source *Type, targets []*Type, targetFlags TypeFlags, newPriority InferencePriority) {
savePriority := n.priority
n.priority |= newPriority
c.inferToMultipleTypes(n, source, targets, targetFlags)
c.inferToMultipleTypes(n, source, targets, targetFlags, nil)
n.priority = savePriority
}

Expand Down Expand Up @@ -1193,6 +1232,16 @@ func (c *Checker) replaceIndexedAccess(instantiable *Type, t *Type, replacement
return c.instantiateType(instantiable, newTypeMapper([]*Type{t.AsIndexedAccessType().indexType, t.AsIndexedAccessType().objectType}, []*Type{c.getNumberLiteralType(0), c.createTupleType([]*Type{replacement})}))
}

func (c *Checker) getInferenceDiscriminants(source *Type, target *Type) []*ast.Symbol {
if target.flags&TypeFlagsObject == 0 {
return nil
}
literalProps := core.Filter(c.getPropertiesOfType(target), func(prop *ast.Symbol) bool {
return isLiteralType(c.getNonMissingTypeOfSymbol(prop))
})
return c.findDiscriminantProperties(literalProps, source)
}

func (c *Checker) typesDefinitelyUnrelated(source *Type, target *Type) bool {
// Two tuple types with incompatible arities are definitely unrelated.
// Two object types that each have a property that is unmatched in the other are definitely unrelated.
Expand Down
58 changes: 45 additions & 13 deletions tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ func (c *Checker) compareTypesAssignableSimple(source *Type, target *Type) Terna
return TernaryFalse
}

func (c *Checker) compareTypesComparableSimple(source *Type, target *Type) Ternary {
if c.isTypeRelatedTo(source, target, c.comparableRelation) {
return TernaryTrue
}
return TernaryFalse
}

func (c *Checker) compareTypesAssignableWorker(source *Type, target *Type, reportErrors bool) Ternary {
if c.isTypeRelatedTo(source, target, c.assignableRelation) {
return TernaryTrue
Expand Down Expand Up @@ -1031,6 +1038,7 @@ type TypeDiscriminator struct {
c *Checker
props []*ast.Symbol
isRelatedTo func(*Type, *Type) Ternary
reverse bool
}

func (d *TypeDiscriminator) len() int {
Expand All @@ -1041,10 +1049,18 @@ func (d *TypeDiscriminator) name(index int) string {
return d.props[index].Name()
}

func (d *TypeDiscriminator) matches(index int, t *Type) bool {
propType := d.c.getTypeOfSymbol(d.props[index])
for _, s := range propType.Distributed() {
if d.isRelatedTo(s, t) != TernaryFalse {
func (d *TypeDiscriminator) matches(index int, t *Type, isOptional bool) bool {
// Two optional discriminants always overlap through absence. Under strictNullChecks this is what the
// relation below would conclude anyway, so skip the property type resolution and the comparisons.
if isOptional && d.c.strictNullChecks && d.props[index].Flags()&ast.SymbolFlagsOptional != 0 {
return true
}
sourceType, targetType := d.c.getTypeOfSymbol(d.props[index]), t
if d.reverse {
sourceType, targetType = targetType, sourceType
}
for _, sourceType := range sourceType.Distributed() {
if d.isRelatedTo(sourceType, targetType) != TernaryFalse {
return true
}
}
Expand Down Expand Up @@ -1197,12 +1213,21 @@ func (c *Checker) mapTypesByKeyProperty(types []*Type, keyPropertyName string) m
}

type Discriminator interface {
len() int // Number of discriminant properties
name(index int) string // Property name of index-th discriminator
matches(index int, t *Type) bool // True if index-th discriminator matches the given type
len() int // Number of discriminant properties
name(index int) string // Property name of index-th discriminator
matches(index int, t *Type, isOptional bool) bool // True if index-th discriminator matches the given type, which is optional if isOptional is set
}

func (c *Checker) discriminateTypeByDiscriminableItems(target *Type, discriminator Discriminator) *Type {
if filtered := c.discriminateTypesByDiscriminableItems(target, discriminator); filtered != nil {
return c.getUnionTypeEx(filtered, UnionReductionNone, nil, nil)
}
return target
}

// Returns the constituents of the union target that are not ruled out by the discriminator, in union order,
// or nil when the discriminator rules out nothing (or everything, which is treated the same way).
func (c *Checker) discriminateTypesByDiscriminableItems(target *Type, discriminator Discriminator) []*Type {
types := target.Types()
include := make([]Ternary, len(types))
for i, t := range types {
Expand All @@ -1217,9 +1242,17 @@ func (c *Checker) discriminateTypeByDiscriminableItems(target *Type, discriminat
matched := false
for i := range types {
if include[i] != TernaryFalse {
targetType := c.getTypeOfPropertyOrIndexSignatureOfType(types[i], discriminator.name(n))
name := discriminator.name(n)
var targetType *Type
isOptional := false
if prop := c.getPropertyOfType(types[i], name); prop != nil {
targetType = c.getTypeOfSymbol(prop)
isOptional = prop.Flags()&ast.SymbolFlagsOptional != 0
} else if indexInfo := c.getApplicableIndexInfoForName(types[i], name); indexInfo != nil {
targetType = c.addOptionalityEx(indexInfo.valueType, true /*isProperty*/, true /*isOptional*/)
}
if targetType != nil {
if discriminator.matches(n, targetType) {
if discriminator.matches(n, targetType, isOptional) {
matched = true
} else {
include[i] = TernaryMaybe
Expand All @@ -1245,12 +1278,11 @@ func (c *Checker) discriminateTypeByDiscriminableItems(target *Type, discriminat
filteredTypes = append(filteredTypes, t)
}
}
filtered := c.getUnionTypeEx(filteredTypes, UnionReductionNone, nil, nil)
if filtered.flags&TypeFlagsNever == 0 {
return filtered
if len(filteredTypes) != 0 {
return filteredTypes
}
}
return target
return nil
}

func (c *Checker) filterPrimitivesIfContainsNonPrimitive(unionType *Type) *Type {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [tests/cases/compiler/classExtendedDiscriminatedUnionInference.ts] ////

//// [classExtendedDiscriminatedUnionInference.ts]
declare class LocalController<T, U> {
isLocal: true;
getProps(a: T, b: U): void;
}
declare class GlobalController<T, U> {
isLocal: false;
// N.B.: Parameter type order is reversed
getProps(a: U, b: T): void;
}

class FailWithTS42 extends LocalController<{ foo: any }, { bar: any }> { }
const p = new FailWithTS42();

declare function createEnhancer1<T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>): [T, U];
const q1 = createEnhancer1(p);
const q2 = createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>); // structurally identical type to the above


//// [classExtendedDiscriminatedUnionInference.js]
"use strict";
class FailWithTS42 extends LocalController {
}
const p = new FailWithTS42();
const q1 = createEnhancer1(p);
const q2 = createEnhancer1(p); // structurally identical type to the above
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//// [tests/cases/compiler/classExtendedDiscriminatedUnionInference.ts] ////

=== classExtendedDiscriminatedUnionInference.ts ===
declare class LocalController<T, U> {
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 30))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 32))

isLocal: true;
>isLocal : Symbol(LocalController.isLocal, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 37))

getProps(a: T, b: U): void;
>getProps : Symbol(LocalController.getProps, Decl(classExtendedDiscriminatedUnionInference.ts, 1, 18))
>a : Symbol(a, Decl(classExtendedDiscriminatedUnionInference.ts, 2, 13))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 30))
>b : Symbol(b, Decl(classExtendedDiscriminatedUnionInference.ts, 2, 18))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 32))
}
declare class GlobalController<T, U> {
>GlobalController : Symbol(GlobalController, Decl(classExtendedDiscriminatedUnionInference.ts, 3, 1))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 31))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 33))

isLocal: false;
>isLocal : Symbol(GlobalController.isLocal, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 38))

// N.B.: Parameter type order is reversed
getProps(a: U, b: T): void;
>getProps : Symbol(GlobalController.getProps, Decl(classExtendedDiscriminatedUnionInference.ts, 5, 19))
>a : Symbol(a, Decl(classExtendedDiscriminatedUnionInference.ts, 7, 13))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 33))
>b : Symbol(b, Decl(classExtendedDiscriminatedUnionInference.ts, 7, 18))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 4, 31))
}

class FailWithTS42 extends LocalController<{ foo: any }, { bar: any }> { }
>FailWithTS42 : Symbol(FailWithTS42, Decl(classExtendedDiscriminatedUnionInference.ts, 8, 1))
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
>foo : Symbol(foo, Decl(classExtendedDiscriminatedUnionInference.ts, 10, 44))
>bar : Symbol(bar, Decl(classExtendedDiscriminatedUnionInference.ts, 10, 58))

const p = new FailWithTS42();
>p : Symbol(p, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 5))
>FailWithTS42 : Symbol(FailWithTS42, Decl(classExtendedDiscriminatedUnionInference.ts, 8, 1))

declare function createEnhancer1<T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>): [T, U];
>createEnhancer1 : Symbol(createEnhancer1, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 29))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))
>Wrapper : Symbol(Wrapper, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 39))
>GlobalController : Symbol(GlobalController, Decl(classExtendedDiscriminatedUnionInference.ts, 3, 1))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))
>T : Symbol(T, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 33))
>U : Symbol(U, Decl(classExtendedDiscriminatedUnionInference.ts, 13, 35))

const q1 = createEnhancer1(p);
>q1 : Symbol(q1, Decl(classExtendedDiscriminatedUnionInference.ts, 14, 5))
>createEnhancer1 : Symbol(createEnhancer1, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 29))
>p : Symbol(p, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 5))

const q2 = createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>); // structurally identical type to the above
>q2 : Symbol(q2, Decl(classExtendedDiscriminatedUnionInference.ts, 15, 5))
>createEnhancer1 : Symbol(createEnhancer1, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 29))
>p : Symbol(p, Decl(classExtendedDiscriminatedUnionInference.ts, 11, 5))
>LocalController : Symbol(LocalController, Decl(classExtendedDiscriminatedUnionInference.ts, 0, 0))
>foo : Symbol(foo, Decl(classExtendedDiscriminatedUnionInference.ts, 15, 49))
>bar : Symbol(bar, Decl(classExtendedDiscriminatedUnionInference.ts, 15, 63))

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//// [tests/cases/compiler/classExtendedDiscriminatedUnionInference.ts] ////

=== classExtendedDiscriminatedUnionInference.ts ===
declare class LocalController<T, U> {
>LocalController : LocalController<T, U>

isLocal: true;
>isLocal : true
>true : true

getProps(a: T, b: U): void;
>getProps : (a: T, b: U) => void
>a : T
>b : U
}
declare class GlobalController<T, U> {
>GlobalController : GlobalController<T, U>

isLocal: false;
>isLocal : false
>false : false

// N.B.: Parameter type order is reversed
getProps(a: U, b: T): void;
>getProps : (a: U, b: T) => void
>a : U
>b : T
}

class FailWithTS42 extends LocalController<{ foo: any }, { bar: any }> { }
>FailWithTS42 : FailWithTS42
>LocalController : LocalController<{ foo: any; }, { bar: any; }>
>foo : any
>bar : any

const p = new FailWithTS42();
>p : FailWithTS42
>new FailWithTS42() : FailWithTS42
>FailWithTS42 : typeof FailWithTS42

declare function createEnhancer1<T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>): [T, U];
>createEnhancer1 : <T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>) => [T, U]
>Wrapper : GlobalController<T, U> | LocalController<T, U>

const q1 = createEnhancer1(p);
>q1 : [{ foo: any; }, { bar: any; }]
>createEnhancer1(p) : [{ foo: any; }, { bar: any; }]
>createEnhancer1 : <T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>) => [T, U]
>p : FailWithTS42

const q2 = createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>); // structurally identical type to the above
>q2 : [{ foo: any; }, { bar: any; }]
>createEnhancer1(p as LocalController<{ foo: any }, { bar: any }>) : [{ foo: any; }, { bar: any; }]
>createEnhancer1 : <T, U>(Wrapper: GlobalController<T, U> | LocalController<T, U>) => [T, U]
>p as LocalController<{ foo: any }, { bar: any }> : LocalController<{ foo: any; }, { bar: any; }>
>p : FailWithTS42
>foo : any
>bar : any

Loading