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
61 changes: 51 additions & 10 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const (
CachedTypeKindDecoratorContextStatic
CachedTypeKindDecoratorContextPrivate
CachedTypeKindDecoratorContextPrivateStatic
CachedTypeKindNonDistributedConditionalType
CachedTypeKindSimplifiedConditionalTypeForReading
CachedTypeKindSimplifiedConditionalTypeForWriting
)
Expand Down Expand Up @@ -22891,7 +22892,12 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type
key := getTypeInstantiationKey(typeArguments, newAlias, t.objectFlags&ObjectFlagsSingleSignatureType != 0)
if data.instantiations == nil {
data.instantiations = make(map[CacheHashKey]*Type)
data.instantiations[getTypeInstantiationKey(typeParameters, target.alias, false)] = target
// The identity instantiation isn't the declared type itself when the type is declared in a
// distributive context, as instantiation maps distributed type parameters back to their
// non-distributed form.
if !c.isInDistributiveContext(declaration, nil /*symbol*/) {
data.instantiations[getTypeInstantiationKey(typeParameters, target.alias, false)] = target
}
}
result := data.instantiations[key]
if result == nil {
Expand Down Expand Up @@ -23544,18 +23550,29 @@ func (c *Checker) getTypeFromTypeReference(node *ast.Node) *Type {
}

func (c *Checker) getDistributedTypeParameter(node *ast.Node, t *Type) *Type {
if t.flags&TypeFlagsTypeParameter != 0 && !t.AsTypeParameter().isDistributed {
for n := node.Parent; n != nil && !ast.IsStatement(n); n = n.Parent {
if ast.IsConditionalTypeNode(n) {
if checkTypeNode := n.AsConditionalTypeNode().CheckType; isSimpleIdentifierTypeReference(checkTypeNode) && c.getSymbolFromTypeReference(checkTypeNode) == t.symbol {
// If node is contained in a distributive conditional type for the given type parameter,
// return the distributed form of the type parameter.
return c.getDistributedTypeFromTypeParameter(t)
if t.flags&TypeFlagsTypeParameter != 0 && !t.AsTypeParameter().isDistributed && c.isInDistributiveContext(node, t.symbol) {
// If node is contained in a distributive conditional type for the given type parameter,
// return the distributed form of the type parameter.
return c.getDistributedTypeFromTypeParameter(t)
}
return t
}

// Returns true if the node is, or is contained in, a distributive conditional type for the given type
// parameter symbol, or for any type parameter when symbol is nil. Types declared in such a context may
// reference distributed type parameters, so their identity instantiation isn't the declared type itself
// (instantiation maps distributed type parameters back to their non-distributed form).
func (c *Checker) isInDistributiveContext(node *ast.Node, symbol *ast.Symbol) bool {
for n := node; n != nil && !ast.IsStatement(n); n = n.Parent {
if ast.IsConditionalTypeNode(n) {
if checkTypeNode := n.AsConditionalTypeNode().CheckType; isSimpleIdentifierTypeReference(checkTypeNode) {
if s := c.getSymbolFromTypeReference(checkTypeNode); s != nil && s.Flags()&ast.SymbolFlagsTypeParameter != 0 && (symbol == nil || s == symbol) {
return true
}
}
}
}
return t
return false
}

func (c *Checker) getDistributedTypeFromTypeParameter(t *Type) *Type {
Expand All @@ -23568,6 +23585,25 @@ func (c *Checker) getDistributedTypeFromTypeParameter(t *Type) *Type {
return tp.distributedType
}

// Returns the given conditional type with references to the distributed form of its check type parameter
// mapped back to the non-distributed form. This is the identity instantiation of the conditional type,
// which is cached in the instantiation map of the conditional type's root.
func (c *Checker) getNonDistributedConditionalType(t *Type) *Type {
checkType := t.AsConditionalType().checkType
typeParameter := getNonDistributedTypeParameter(checkType)
if typeParameter == checkType {
return t
}
key := CachedTypeKey{kind: CachedTypeKindNonDistributedConditionalType, typeId: t.id}
if cached := c.cachedTypes[key]; cached != nil {
return cached
}
result := c.instantiateType(t, newSimpleTypeMapper(typeParameter, typeParameter))
debug.Assert(result.flags&TypeFlagsConditional != 0, "Identity instantiation of a conditional type should remain deferred")
c.cachedTypes[key] = result
return result
}

func getNonDistributedTypeParameter(t *Type) *Type {
if t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isDistributed {
return t.AsTypeParameter().constraint
Expand Down Expand Up @@ -24863,7 +24899,12 @@ func (c *Checker) getTypeFromConditionalTypeNode(node *ast.Node) *Type {
links.resolvedType = c.getConditionalType(root, nil /*mapper*/, false /*forConstraint*/, nil)
if outerTypeParameters != nil {
root.instantiations = make(map[CacheHashKey]*Type)
root.instantiations[getConditionalTypeKey(outerTypeParameters, nil /*alias*/, false /*forConstraint*/)] = links.resolvedType
// The identity instantiation isn't the declared type itself when the type is declared in a
// distributive context, as instantiation maps distributed type parameters back to their
// non-distributed form.
if !c.isInDistributiveContext(node, nil /*symbol*/) {
root.instantiations[getConditionalTypeKey(outerTypeParameters, nil /*alias*/, false /*forConstraint*/)] = links.resolvedType
}
}
}
return links.resolvedType
Expand Down
9 changes: 5 additions & 4 deletions tsc/internal/checker/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,11 @@ func (c *Checker) inferToMultipleTypesWithPriority(n *InferenceState, source *Ty

func (c *Checker) inferToConditionalType(n *InferenceState, source *Type, target *Type) {
if source.flags&TypeFlagsConditional != 0 {
c.inferFromTypes(n, getNonDistributedTypeParameter(source.AsConditionalType().checkType), target.AsConditionalType().checkType)
c.inferFromTypes(n, getNonDistributedTypeParameter(source.AsConditionalType().extendsType), target.AsConditionalType().extendsType)
c.inferFromTypes(n, getNonDistributedTypeParameter(c.getTrueTypeFromConditionalType(source)), c.getTrueTypeFromConditionalType(target))
c.inferFromTypes(n, getNonDistributedTypeParameter(c.getFalseTypeFromConditionalType(source)), c.getFalseTypeFromConditionalType(target))
source := c.getNonDistributedConditionalType(source)
c.inferFromTypes(n, source.AsConditionalType().checkType, target.AsConditionalType().checkType)
c.inferFromTypes(n, source.AsConditionalType().extendsType, target.AsConditionalType().extendsType)
c.inferFromTypes(n, c.getTrueTypeFromConditionalType(source), c.getTrueTypeFromConditionalType(target))
c.inferFromTypes(n, c.getFalseTypeFromConditionalType(source), c.getFalseTypeFromConditionalType(target))
} else {
targetTypes := []*Type{c.getTrueTypeFromConditionalType(target), c.getFalseTypeFromConditionalType(target)}
c.inferToMultipleTypesWithPriority(n, source, targetTypes, target.flags, core.IfElse(n.contravariant, InferencePriorityContravariantConditional, 0))
Expand Down
27 changes: 17 additions & 10 deletions tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -3360,13 +3360,15 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
}
case source.flags&TypeFlagsConditional != 0:
if source.AsConditionalType().root.isDistributive == target.AsConditionalType().root.isDistributive {
result = r.isRelatedTo(source.AsConditionalType().checkType, target.AsConditionalType().checkType, RecursionFlagsBoth, false /*reportErrors*/)
s := r.c.getNonDistributedConditionalType(source)
t := r.c.getNonDistributedConditionalType(target)
result = r.isRelatedTo(s.AsConditionalType().checkType, t.AsConditionalType().checkType, RecursionFlagsBoth, false /*reportErrors*/)
if result != TernaryFalse {
result &= r.isRelatedTo(source.AsConditionalType().extendsType, target.AsConditionalType().extendsType, RecursionFlagsBoth, false /*reportErrors*/)
result &= r.isRelatedTo(s.AsConditionalType().extendsType, t.AsConditionalType().extendsType, RecursionFlagsBoth, false /*reportErrors*/)
if result != TernaryFalse {
result &= r.isRelatedTo(r.c.getTrueTypeFromConditionalType(source), r.c.getTrueTypeFromConditionalType(target), RecursionFlagsBoth, false /*reportErrors*/)
result &= r.isRelatedTo(r.c.getTrueTypeFromConditionalType(s), r.c.getTrueTypeFromConditionalType(t), RecursionFlagsBoth, false /*reportErrors*/)
if result != TernaryFalse {
result &= r.isRelatedTo(r.c.getFalseTypeFromConditionalType(source), r.c.getFalseTypeFromConditionalType(target), RecursionFlagsBoth, false /*reportErrors*/)
result &= r.isRelatedTo(r.c.getFalseTypeFromConditionalType(s), r.c.getFalseTypeFromConditionalType(t), RecursionFlagsBoth, false /*reportErrors*/)
if result != TernaryFalse {
return result
}
Expand Down Expand Up @@ -3758,23 +3760,28 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
return TernaryMaybe
}
if target.flags&TypeFlagsConditional != 0 {
// The constituent types of an uninstantiated distributive conditional type reference the distributed
// form of the check type parameter. Normalize both sides to their non-distributed form such that the
// comparison doesn't depend on whether either side has been instantiated.
s := r.c.getNonDistributedConditionalType(source)
t := r.c.getNonDistributedConditionalType(target)
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.
sourceParams := source.AsConditionalType().root.inferTypeParameters
sourceExtends := source.AsConditionalType().extendsType
sourceParams := s.AsConditionalType().root.inferTypeParameters
sourceExtends := s.AsConditionalType().extendsType
var mapper *TypeMapper
if len(sourceParams) != 0 {
// If the source has infer type parameters, we instantiate them in the context of the target
ctx := r.c.newInferenceContext(sourceParams, nil /*signature*/, InferenceFlagsNone, r.isRelatedToWorker)
r.c.inferTypes(ctx.inferences, target.AsConditionalType().extendsType, sourceExtends, InferencePriorityNoConstraints|InferencePriorityAlwaysStrict, false)
r.c.inferTypes(ctx.inferences, t.AsConditionalType().extendsType, sourceExtends, InferencePriorityNoConstraints|InferencePriorityAlwaysStrict, false)
sourceExtends = r.c.instantiateType(sourceExtends, ctx.mapper)
mapper = ctx.mapper
}
if r.c.isTypeIdenticalTo(sourceExtends, target.AsConditionalType().extendsType) && (r.isRelatedTo(source.AsConditionalType().checkType, target.AsConditionalType().checkType, RecursionFlagsBoth, false) != 0 || r.isRelatedTo(target.AsConditionalType().checkType, source.AsConditionalType().checkType, RecursionFlagsBoth, false) != 0) {
result = r.isRelatedTo(r.c.instantiateType(r.c.getTrueTypeFromConditionalType(source), mapper), r.c.getTrueTypeFromConditionalType(target), RecursionFlagsBoth, reportErrors)
if r.c.isTypeIdenticalTo(sourceExtends, t.AsConditionalType().extendsType) && (r.isRelatedTo(s.AsConditionalType().checkType, t.AsConditionalType().checkType, RecursionFlagsBoth, false) != 0 || r.isRelatedTo(t.AsConditionalType().checkType, s.AsConditionalType().checkType, RecursionFlagsBoth, false) != 0) {
result = r.isRelatedTo(r.c.instantiateType(r.c.getTrueTypeFromConditionalType(s), mapper), r.c.getTrueTypeFromConditionalType(t), RecursionFlagsBoth, reportErrors)
if result != TernaryFalse {
result &= r.isRelatedTo(r.c.getFalseTypeFromConditionalType(source), r.c.getFalseTypeFromConditionalType(target), RecursionFlagsBoth, reportErrors)
result &= r.isRelatedTo(r.c.getFalseTypeFromConditionalType(s), r.c.getFalseTypeFromConditionalType(t), RecursionFlagsBoth, reportErrors)
}
if result != TernaryFalse {
return result
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//// [tests/cases/compiler/distributiveConditionalCallback.ts] ////

//// [distributiveConditionalCallback.ts]
// https://github.com/microsoft/TypeScript/issues/64892

export class C {
m1<T>(
item: T,
callback: T extends string
? (item: T) => void
: (item: T) => void
): void {
callback(item);
callback(item as T extends string ? T : T);
}

m2(
item: string,
callback?: (item: string) => void
): void;
m2<T>(
item: T,
callback: (item: T) => void
): void;
m2<T>(
item: T,
callback: T extends string
? ((item: T) => void) | undefined
: (item: T) => void
): void {
if (callback) {
callback(item);
callback(item as T extends string ? T : T);
}
}
}


//// [distributiveConditionalCallback.js]
// https://github.com/microsoft/TypeScript/issues/64892
export class C {
m1(item, callback) {
callback(item);
callback(item);
}
m2(item, callback) {
if (callback) {
callback(item);
callback(item);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//// [tests/cases/compiler/distributiveConditionalCallback.ts] ////

=== distributiveConditionalCallback.ts ===
// https://github.com/microsoft/TypeScript/issues/64892

export class C {
>C : Symbol(C, Decl(distributiveConditionalCallback.ts, 0, 0))

m1<T>(
>m1 : Symbol(C.m1, Decl(distributiveConditionalCallback.ts, 2, 16))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))

item: T,
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 3, 10))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))

callback: T extends string
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 4, 16))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))

? (item: T) => void
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 6, 15))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))

: (item: T) => void
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 7, 15))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))

): void {
callback(item);
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 4, 16))
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 3, 10))

callback(item as T extends string ? T : T);
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 4, 16))
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 3, 10))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 3, 7))
}

m2(
>m2 : Symbol(C.m2, Decl(distributiveConditionalCallback.ts, 11, 5), Decl(distributiveConditionalCallback.ts, 16, 12), Decl(distributiveConditionalCallback.ts, 20, 12))

item: string,
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 13, 7))

callback?: (item: string) => void
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 14, 21))
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 15, 20))

): void;
m2<T>(
>m2 : Symbol(C.m2, Decl(distributiveConditionalCallback.ts, 11, 5), Decl(distributiveConditionalCallback.ts, 16, 12), Decl(distributiveConditionalCallback.ts, 20, 12))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 17, 7))

item: T,
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 17, 10))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 17, 7))

callback: (item: T) => void
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 18, 16))
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 19, 19))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 17, 7))

): void;
m2<T>(
>m2 : Symbol(C.m2, Decl(distributiveConditionalCallback.ts, 11, 5), Decl(distributiveConditionalCallback.ts, 16, 12), Decl(distributiveConditionalCallback.ts, 20, 12))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))

item: T,
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 21, 10))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))

callback: T extends string
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 22, 16))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))

? ((item: T) => void) | undefined
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 24, 16))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))

: (item: T) => void
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 25, 15))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))

): void {
if (callback) {
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 22, 16))

callback(item);
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 22, 16))
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 21, 10))

callback(item as T extends string ? T : T);
>callback : Symbol(callback, Decl(distributiveConditionalCallback.ts, 22, 16))
>item : Symbol(item, Decl(distributiveConditionalCallback.ts, 21, 10))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))
>T : Symbol(T, Decl(distributiveConditionalCallback.ts, 21, 7))
}
}
}

Loading