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
54 changes: 45 additions & 9 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6749,9 +6749,9 @@ func (c *Checker) getIterationTypesOfMethod(t *Type, resolver *IterationTypesRes
mapper := methodType.Mapper()
var nextType *Type
if methodName == "next" {
nextType = mapper.Map(typeParameters[2])
nextType = getMappedType(typeParameters[2], mapper)
}
return IterationTypes{mapper.Map(typeParameters[0]), mapper.Map(typeParameters[1]), nextType}
return IterationTypes{getMappedType(typeParameters[0], mapper), getMappedType(typeParameters[1], mapper), nextType}
}
}
// Extract the first parameter and return type of each signature.
Expand Down Expand Up @@ -21403,7 +21403,7 @@ func (c *Checker) getArrayMemberCallSignatures(t *Type) []*Signature {
}
// Transform the type from `(A[] | B[])["member"]` to `(A | B)[]["member"]` (since we pretend array is covariant anyway).
arrayArg := c.mapType(t, func(t *Type) *Type {
return t.Mapper().Map(core.IfElse(c.isReadonlyArraySymbol(t.symbol.Parent), c.globalReadonlyArrayType, c.globalArrayType).AsInterfaceType().TypeParameters()[0])
return getMappedType(core.IfElse(c.isReadonlyArraySymbol(t.symbol.Parent), c.globalReadonlyArrayType, c.globalArrayType).AsInterfaceType().TypeParameters()[0], t.Mapper())
})
arrayType := c.createArrayTypeEx(arrayArg, someType(t, func(t *Type) bool {
return c.isReadonlyArraySymbol(t.symbol.Parent)
Expand Down Expand Up @@ -22562,7 +22562,7 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias
flags := t.flags
switch {
case flags&TypeFlagsTypeParameter != 0:
return m.Map(t)
return getMappedType(t, m)
case flags&TypeFlagsObject != 0:
objectFlags := t.objectFlags
if objectFlags&(ObjectFlagsReference|ObjectFlagsAnonymous|ObjectFlagsMapped) != 0 {
Expand Down Expand Up @@ -22837,7 +22837,7 @@ func (c *Checker) getConditionalTypeInstantiation(t *Type, mapper *TypeMapper, f
checkType := root.checkType
var distributionType *Type
if root.isDistributive {
distributionType = c.getReducedType(newMapper.Map(checkType))
distributionType = c.getReducedType(getMappedType(checkType, newMapper))
}
// Distributive conditional types are distributed over union types. For example, when the
// distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the
Expand Down Expand Up @@ -23352,12 +23352,48 @@ func (c *Checker) getTypeFromTypeReference(node *ast.Node) *Type {
} else if t := c.getIntendedTypeFromJSDocTypeReference(node); t != nil {
links.resolvedType = t
} else {
links.resolvedType = c.getTypeReferenceType(node, c.getSymbolFromTypeReference(node))
links.resolvedType = c.getDistributedTypeParameter(node, c.getTypeReferenceType(node, c.getSymbolFromTypeReference(node)))
}
}
return links.resolvedType
}

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)
}
}
}
}
return t
}

func (c *Checker) getDistributedTypeFromTypeParameter(t *Type) *Type {
tp := t.AsTypeParameter()
if tp.distributedType == nil {
tp.distributedType = c.newTypeParameter(t.symbol)
tp.distributedType.AsTypeParameter().isDistributed = true
tp.distributedType.AsTypeParameter().constraint = t
}
return tp.distributedType
}

func getNonDistributedTypeParameter(t *Type) *Type {
if t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isDistributed {
return t.AsTypeParameter().constraint
}
return t
}

func isSimpleIdentifierTypeReference(node *ast.Node) bool {
return ast.IsTypeReferenceNode(node) && ast.IsIdentifier(node.AsTypeReferenceNode().TypeName) && node.TypeArgumentList() == nil
}

func (c *Checker) getIntendedTypeFromJSDocTypeReference(node *ast.Node) *Type {
if node.Flags&ast.NodeFlagsJSDoc != 0 && ast.IsTypeReferenceNode(node) {
typeName := node.AsTypeReferenceNode().TypeName
Expand Down Expand Up @@ -24802,11 +24838,11 @@ func (c *Checker) getTailRecursionRoot(newType *Type, newMapper *TypeMapper) (*C
newRoot := newType.AsConditionalType().root
if len(newRoot.outerTypeParameters) != 0 {
typeParamMapper := c.combineTypeMappers(newType.AsConditionalType().mapper, newMapper)
typeArguments := core.Map(newRoot.outerTypeParameters, func(t *Type) *Type { return typeParamMapper.Map(t) })
typeArguments := core.Map(newRoot.outerTypeParameters, typeParamMapper.Map)
newRootMapper := newTypeMapper(newRoot.outerTypeParameters, typeArguments)
var newCheckType *Type
if newRoot.isDistributive {
newCheckType = newRootMapper.Map(newRoot.checkType)
newCheckType = getMappedType(newRoot.checkType, newRootMapper)
}
if newCheckType == nil || newCheckType == newRoot.checkType || newCheckType.flags&(TypeFlagsUnion|TypeFlagsNever) == 0 {
return newRoot, newRootMapper
Expand Down Expand Up @@ -28490,7 +28526,7 @@ func (c *Checker) getModifiersTypeFromMappedType(t *Type) *Type {
constraint := c.getConstraintTypeFromMappedType(declaredType)
extendedConstraint := constraint
if constraint != nil && constraint.flags&TypeFlagsTypeParameter != 0 {
extendedConstraint = c.getConstraintOfTypeParameter(constraint)
extendedConstraint = c.getConstraintOfTypeParameter(getNonDistributedTypeParameter(constraint))
}
if extendedConstraint != nil && extendedConstraint.flags&TypeFlagsIndex != 0 {
m.modifiersType = c.instantiateType(extendedConstraint.AsIndexType().target, m.mapper)
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,3 +386,7 @@ func (c *Checker) GetWidenedType(t *Type) *Type {
func (c *Checker) CompareSymbols(s1, s2 *ast.Symbol) int {
return c.compareSymbols(s1, s2)
}

func IsDistributedTypeParameter(t *Type) bool {
return t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isDistributed
}
3 changes: 2 additions & 1 deletion tsc/internal/checker/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ func (c *Checker) inferToConditionalType(n *InferenceState, source *Type, target
}

func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, target *TemplateLiteralType) {
matches := c.inferTypesFromTemplateLiteralType(source, target)
matches := c.inferTypesFromTemplateLiteralType(source, target, c.compareTypesAssignable)
types := target.types
// When the target template literal contains only placeholders (meaning that inference is intended to extract
// single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for
Expand Down Expand Up @@ -1518,6 +1518,7 @@ func (c *Checker) getTypeFromInference(inference *InferenceInfo) *Type {

func getInferenceInfoForType(n *InferenceState, t *Type) *InferenceInfo {
if t.flags&TypeFlagsTypeVariable != 0 {
t = getNonDistributedTypeParameter(t)
for _, inference := range n.inferences {
if t == inference.typeParameter {
return inference
Expand Down
18 changes: 11 additions & 7 deletions tsc/internal/checker/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ type TypeMapperData interface {

// Factory functions

func getMappedType(t *Type, mapper *TypeMapper) *Type {
return mapper.Map(getNonDistributedTypeParameter(t))
}

func newTypeMapper(sources []*Type, targets []*Type) *TypeMapper {
if len(sources) == 1 {
return newSimpleTypeMapper(sources[0], targets[0])
Expand All @@ -53,13 +57,13 @@ func (c *Checker) combineTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper

func (c *Checker) mapTypeWithCompositeMapper(t *Type, m1 *TypeMapper, m2 *TypeMapper) *Type {
if m1 == nil {
return m2.Map(t)
return getMappedType(t, m2)
}
t1 := m1.Map(t)
t1 := getMappedType(t, m1)
if t1 != t {
return c.instantiateType(t1, m2)
}
return m2.Map(t)
return getMappedType(t, m2)
}

func mergeTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper {
Expand All @@ -71,16 +75,16 @@ func mergeTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper {

func prependTypeMapping(source *Type, target *Type, mapper *TypeMapper) *TypeMapper {
if mapper == nil {
return newSimpleTypeMapper(source, target)
return newSimpleTypeMapper(getNonDistributedTypeParameter(source), target)
}
return newMergedTypeMapper(newSimpleTypeMapper(source, target), mapper)
return newMergedTypeMapper(newSimpleTypeMapper(getNonDistributedTypeParameter(source), target), mapper)
}

func appendTypeMapping(mapper *TypeMapper, source *Type, target *Type) *TypeMapper {
if mapper == nil {
return newSimpleTypeMapper(source, target)
return newSimpleTypeMapper(getNonDistributedTypeParameter(source), target)
}
return newMergedTypeMapper(mapper, newSimpleTypeMapper(source, target))
return newMergedTypeMapper(mapper, newSimpleTypeMapper(getNonDistributedTypeParameter(source), target))
}

// Maps forward-references to later types parameters to the empty object type.
Expand Down
18 changes: 10 additions & 8 deletions tsc/internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -3290,14 +3290,6 @@ func (b *NodeBuilderImpl) visitAndTransformType(t *Type, transform func(b *NodeB
}

func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
// Push type onto typeStack for expansion depth tracking
if b.ctx.maxExpansionDepth >= 0 && t != nil {
b.ctx.typeStack = append(b.ctx.typeStack, t)
defer func() {
b.ctx.typeStack = b.ctx.typeStack[:len(b.ctx.typeStack)-1]
}()
}

inTypeAlias := b.ctx.flags & nodebuilder.FlagsInTypeAlias
b.ctx.flags &^= nodebuilder.FlagsInTypeAlias

Expand All @@ -3311,6 +3303,16 @@ func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
return b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
}

t = getNonDistributedTypeParameter(t)

// Push type onto typeStack for expansion depth tracking
if b.ctx.maxExpansionDepth >= 0 {
b.ctx.typeStack = append(b.ctx.typeStack, t)
defer func() {
b.ctx.typeStack = b.ctx.typeStack[:len(b.ctx.typeStack)-1]
}()
}

if b.ctx.flags&nodebuilder.FlagsNoTypeReduction == 0 {
t = b.ch.getReducedType(t)
}
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/nodecopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ func getExistingNodeTreeVisitor(b *NodeBuilderImpl, bound *recoveryBoundary) *as
}
if s.Flags&ast.SymbolFlagsTypeParameter != 0 {
declaredType := b.ch.getDeclaredTypeOfSymbol(s)
if b.ctx.mapper != nil && b.ctx.mapper.Map(declaredType) != declaredType {
if b.ctx.mapper != nil && getMappedType(declaredType, b.ctx.mapper) != declaredType {
return nil // refers to type parameter remapped by context (TODO improvement: just return the remapped param name?)
}
}
Expand Down
8 changes: 5 additions & 3 deletions tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -2363,7 +2363,7 @@ func (c *Checker) templateLiteralTypesDefinitelyUnrelated(source *TemplateLitera
}

func (c *Checker) isTypeMatchedByTemplateLiteralType(source *Type, target *TemplateLiteralType, compareTypes TypeComparer) bool {
inferences := c.inferTypesFromTemplateLiteralType(source, target)
inferences := c.inferTypesFromTemplateLiteralType(source, target, compareTypes)
if inferences != nil {
for i, inference := range inferences {
if !c.isValidTypeForTemplateLiteralPlaceholder(inference, target.types[i], compareTypes) {
Expand All @@ -2375,14 +2375,14 @@ func (c *Checker) isTypeMatchedByTemplateLiteralType(source *Type, target *Templ
return false
}

func (c *Checker) inferTypesFromTemplateLiteralType(source *Type, target *TemplateLiteralType) []*Type {
func (c *Checker) inferTypesFromTemplateLiteralType(source *Type, target *TemplateLiteralType, compareTypes TypeComparer) []*Type {
switch {
case source.flags&TypeFlagsStringLiteral != 0:
return c.inferFromLiteralPartsToTemplateLiteral([]string{getStringLiteralValue(source)}, nil, target)
case source.flags&TypeFlagsTemplateLiteral != 0:
if slices.Equal(source.AsTemplateLiteralType().texts, target.texts) {
return core.MapIndex(source.AsTemplateLiteralType().types, func(s *Type, i int) *Type {
if c.isTypeAssignableTo(c.getBaseConstraintOrType(s), c.getBaseConstraintOrType(target.types[i])) {
if compareTypes(c.getBaseConstraintOrType(s), c.getBaseConstraintOrType(target.types[i]), false /*partialMatch*/) != TernaryFalse {
return s
}
return c.getStringLikeTypeForType(s)
Expand Down Expand Up @@ -4800,6 +4800,8 @@ func (r *Relater) reportRelationError(message *diagnostics.Message, source *Type
if targetFlags&TypeFlagsTypeParameter != 0 && target != r.c.markerSuperTypeForCheck && target != r.c.markerSubTypeForCheck {
constraint := r.c.getBaseConstraintOfType(target)
switch {
case IsDistributedTypeParameter(target) && r.c.isTypeAssignableTo(generalizedSource, target.AsTypeParameter().constraint):
r.reportError(diagnostics.X_0_is_only_assignable_to_the_non_distributed_1_and_1_has_been_distributed_here, generalizedSourceType, targetType)
case constraint != nil && r.c.isTypeAssignableTo(generalizedSource, constraint):
r.reportError(diagnostics.X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, generalizedSourceType, targetType, r.c.TypeToString(constraint))
case constraint != nil && r.c.isTypeAssignableTo(source, constraint):
Expand Down
2 changes: 2 additions & 0 deletions tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1170,7 +1170,9 @@ type TypeParameter struct {
target *Type
mapper *TypeMapper
isThisType bool
isDistributed bool
resolvedDefaultType *Type
distributedType *Type
}

func (t *TypeParameter) IsThisType() bool { return t.isThisType }
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/diagnostics/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4768,6 +4768,10 @@
"category": "Error",
"code": 5112
},
"'{0}' is only assignable to the non-distributed '{1}' and '{1}' has been distributed here.": {
"category": "Error",
"code": 5113
},

"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/diagnostics/diagnostics_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestQuickInfoDistributedTypeParameter(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `type Conditional<T> =
T/*check*/ extends T/*extends*/
? T/*trueType*/
: T/*falseType*/;

type NonDistributed<T> = [T/*nonDistributed*/] extends [unknown] ? T : never;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "check", "(type parameter) (distributed) T in type Conditional<T>", "")
f.VerifyQuickInfoAt(t, "extends", "(type parameter) (distributed) T in type Conditional<T>", "")
f.VerifyQuickInfoAt(t, "trueType", "(type parameter) (distributed) T in type Conditional<T>", "")
f.VerifyQuickInfoAt(t, "falseType", "(type parameter) (distributed) T in type Conditional<T>", "")
f.VerifyQuickInfoAt(t, "nonDistributed", "(type parameter) T in type NonDistributed<T>", "")
}
5 changes: 5 additions & 0 deletions tsc/internal/ls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,11 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol
dpw.WritePunctuation("(")
dpw.Write("type parameter")
dpw.WritePunctuation(") ")
if ast.IsIdentifier(node) && ast.IsTypeReferenceNode(node.Parent) && checker.IsDistributedTypeParameter(c.GetTypeAtLocation(node.Parent)) {
dpw.WritePunctuation("(")
dpw.Write("distributed")
dpw.WritePunctuation(") ")
}
Comment on lines +876 to +880

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in fd9306a by adding a Fourslash quick-info regression test covering distributed check, extends, true, and false type occurrences plus a non-distributed occurrence.

tp := c.GetDeclaredTypeOfSymbol(symbol)
writeSymbolClassified(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags)
cons := c.GetConstraintOfTypeParameter(tp)
Expand Down
Loading