-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Use objects instead of closures for type mappers #36576
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
63338f9
979bc6a
82cd7a1
8e6a2b1
c7d3806
163ba2f
61f0c7a
50de2db
ce9ddf3
30e7a18
1cb54e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -305,7 +305,6 @@ namespace ts { | |
let currentNode: Node | undefined; | ||
|
||
const emptySymbols = createSymbolTable(); | ||
const identityMapper: (type: Type) => Type = identity; | ||
const arrayVariances = [VarianceFlags.Covariant]; | ||
|
||
const compilerOptions = host.getCompilerOptions(); | ||
|
@@ -729,6 +728,10 @@ namespace ts { | |
const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; | ||
const numberOrBigIntType = getUnionType([numberType, bigintType]); | ||
|
||
const identityMapper: TypeMapper = makeUnaryTypeMapper(anyType, anyType); | ||
const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(<TypeParameter>t) : t); | ||
const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t); | ||
|
||
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); | ||
const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); | ||
emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; | ||
|
@@ -4957,7 +4960,7 @@ namespace ts { | |
const params = getTypeParametersOfClassOrInterface( | ||
parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol | ||
); | ||
typeParameterNodes = mapToTypeNodes(map(params, (nextSymbol as TransientSymbol).mapper!), context); | ||
typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).mapper!)), context); | ||
} | ||
else { | ||
typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); | ||
|
@@ -9636,7 +9639,7 @@ namespace ts { | |
// Create a mapper from T to the current iteration type constituent. Then, if the | ||
// mapped type is itself an instantiated type, combine the iteration mapper with the | ||
// instantiation mapper. | ||
const templateMapper = combineTypeMappers(type.mapper, createTypeMapper([typeParameter], [t])); | ||
const templateMapper = addTypeMapping(type.mapper, typeParameter, t); | ||
// If the current iteration type constituent is a string literal type, create a property. | ||
// Otherwise, for type string create a string index signature. | ||
if (isTypeUsableAsPropertyName(t)) { | ||
|
@@ -9689,6 +9692,7 @@ namespace ts { | |
type = errorType; | ||
} | ||
symbol.type = type; | ||
symbol.mapper = undefined!; | ||
amcasey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return symbol.type; | ||
} | ||
|
@@ -13538,30 +13542,50 @@ namespace ts { | |
return instantiateList<Signature>(signatures, mapper, instantiateSignature); | ||
} | ||
|
||
function makeUnaryTypeMapper(source: Type, target: Type) { | ||
return (t: Type) => t === source ? target : t; | ||
function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { | ||
return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : | ||
amcasey marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kinda-sorta wanna preemptively make this a loop rather than a recursive function to better optimize the recursive cases, but... it's not strictly required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked at that, but just makes the code more complex for no appreciable gain. |
||
sources.length === 2 ? makeSimpleTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) : | ||
makeArrayTypeMapper(sources, targets); | ||
} | ||
|
||
function makeBinaryTypeMapper(source1: Type, target1: Type, source2: Type, target2: Type) { | ||
return (t: Type) => t === source1 ? target1 : t === source2 ? target2 : t; | ||
function getMappedType(type: Type, map: TypeMapper): Type { | ||
switch (map.kind) { | ||
case TypeMapKind.Simple: | ||
return type === map.source1 ? map.target1 : type === map.source2 ? map.target2 : type; | ||
case TypeMapKind.Array: | ||
const sources = map.sources; | ||
const targets = map.targets; | ||
for (let i = 0; i < sources.length; i++) { | ||
if (type === sources[i]) { | ||
return targets ? targets[i] : anyType; | ||
} | ||
} | ||
return type; | ||
case TypeMapKind.Function: | ||
return map.func(type); | ||
case TypeMapKind.Composite: | ||
return instantiateType(getMappedType(type, map.mapper1), map.mapper2); | ||
} | ||
} | ||
|
||
function makeArrayTypeMapper(sources: readonly Type[], targets: readonly Type[] | undefined) { | ||
return (t: Type) => { | ||
for (let i = 0; i < sources.length; i++) { | ||
if (t === sources[i]) { | ||
return targets ? targets[i] : anyType; | ||
} | ||
} | ||
return t; | ||
}; | ||
function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper { | ||
return makeSimpleTypeMapper(source, target, anyType, anyType); | ||
} | ||
|
||
function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { | ||
Debug.assert(targets === undefined || sources.length === targets.length); | ||
return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : | ||
sources.length === 2 ? makeBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) : | ||
makeArrayTypeMapper(sources, targets); | ||
function makeSimpleTypeMapper(source1: Type, target1: Type, source2: Type, target2: Type): TypeMapper { | ||
return { kind: TypeMapKind.Simple, source1, target1, source2, target2 }; | ||
} | ||
|
||
function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { | ||
return { kind: TypeMapKind.Array, sources, targets }; | ||
} | ||
|
||
function makeFunctionTypeMapper(func: (t: Type) => Type): TypeMapper { | ||
return { kind: TypeMapKind.Function, func }; | ||
} | ||
|
||
function makeCompositeTypeMapper(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { | ||
return { kind: TypeMapKind.Composite, mapper1, mapper2 }; | ||
} | ||
|
||
function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { | ||
|
@@ -13573,23 +13597,30 @@ namespace ts { | |
* This is used during inference when instantiating type parameter defaults. | ||
*/ | ||
function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { | ||
return t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t; | ||
return makeFunctionTypeMapper(t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t); | ||
} | ||
|
||
function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper; | ||
function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper | undefined): TypeMapper; | ||
function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { | ||
if (!mapper1) return mapper2; | ||
if (!mapper2) return mapper1; | ||
return t => instantiateType(mapper1(t), mapper2); | ||
return !mapper1 ? mapper2 : !mapper2 ? mapper1 : makeCompositeTypeMapper(mapper1, mapper2); | ||
} | ||
|
||
function createReplacementMapper(source: Type, target: Type, baseMapper: TypeMapper): TypeMapper { | ||
return t => t === source ? target : baseMapper(t); | ||
function addTypeMapping(mapper: TypeMapper | undefined, source: TypeParameter, target: Type) { | ||
return mapper && mapper.kind === TypeMapKind.Simple && mapper.source2 === mapper.target2 ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm getting mixed up. When you combine two maps, you look in the first map first and then, if you find something, do you stop looking or apply the second map to the first target? From the handling of composite maps in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The particular test here is to see if the second source/target pair in the simple mapper is unused (recall, when source and target are the same, we have a no-op). If so, we create a new simple mapper with both source/target pairs in use. One added twist with composite mappers is that the first mapper may map to some type that the second mapper further maps. For example, the first mapper might map from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should add, in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So maps are combined by composition, but we happen to know that this particular composition is equivalent to concatenation? |
||
makeSimpleTypeMapper(mapper.source1, mapper.target1, source, target) : | ||
combineTypeMappers(mapper, makeUnaryTypeMapper(source, target)); | ||
} | ||
|
||
function permissiveMapper(type: Type) { | ||
return type.flags & TypeFlags.TypeParameter ? wildcardType : type; | ||
function createReplacementMapper(source: Type, target: Type, baseMapper: TypeMapper): TypeMapper { | ||
switch (baseMapper.kind) { | ||
case TypeMapKind.Simple: | ||
return makeSimpleTypeMapper(baseMapper.source1, baseMapper.source1 === source ? target : baseMapper.target1, | ||
baseMapper.source2, baseMapper.source2 === source ? target : baseMapper.target2); | ||
case TypeMapKind.Array: | ||
return makeArrayTypeMapper(baseMapper.sources, map(baseMapper.targets, (t, i) => baseMapper.sources[i] === source ? target : t)); | ||
} | ||
return makeFunctionTypeMapper(t => t === source ? target : getMappedType(t, baseMapper)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on how badly I've mixed up composite maps (see my question above), this seems like it might be equivalent to a composite map with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not quite. We don't want the second mapping to be applied to the result of the first. We basically just want to replace one of the mappings in the second mapper, which we can do by putting a check in front of the second mapper. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come this transformation (concatenation?) wasn't interesting enough to become a |
||
} | ||
|
||
function getRestrictiveTypeParameter(tp: TypeParameter) { | ||
|
@@ -13600,10 +13631,6 @@ namespace ts { | |
); | ||
} | ||
|
||
function restrictiveMapper(type: Type) { | ||
return type.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(<TypeParameter>type) : type; | ||
} | ||
|
||
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { | ||
const result = createTypeParameter(typeParameter.symbol); | ||
result.target = typeParameter; | ||
|
@@ -13643,7 +13670,7 @@ namespace ts { | |
|
||
function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { | ||
const links = getSymbolLinks(symbol); | ||
if (links.type && !maybeTypeOfKind(links.type, TypeFlags.Object | TypeFlags.Instantiable)) { | ||
if (links.type && !couldContainTypeVariables(links.type)) { | ||
// If the type of the symbol is already resolved, and if that type could not possibly | ||
// be affected by instantiation, simply return the symbol itself. | ||
return symbol; | ||
|
@@ -13710,7 +13737,8 @@ namespace ts { | |
// We are instantiating an anonymous type that has one or more type parameters in scope. Apply the | ||
// mapper to the type parameters to produce the effective list of type arguments, and compute the | ||
// instantiation cache key from the type IDs of the type arguments. | ||
const typeArguments = map(typeParameters, combineTypeMappers(type.mapper, mapper)); | ||
const combinedMapper = combineTypeMappers(type.mapper, mapper); | ||
const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper)); | ||
const id = getTypeListId(typeArguments); | ||
let result = links.instantiations!.get(id); | ||
if (!result) { | ||
|
@@ -13823,7 +13851,7 @@ namespace ts { | |
} | ||
|
||
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { | ||
const templateMapper = combineTypeMappers(mapper, createTypeMapper([getTypeParameterFromMappedType(type)], [key])); | ||
const templateMapper = addTypeMapping(mapper, getTypeParameterFromMappedType(type), key); | ||
const propType = instantiateType(getTemplateTypeFromMappedType(<MappedType>type.target || type), templateMapper); | ||
const modifiers = getMappedTypeModifiers(type); | ||
return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType) : | ||
|
@@ -13855,7 +13883,7 @@ namespace ts { | |
// We are instantiating a conditional type that has one or more type parameters in scope. Apply the | ||
// mapper to the type parameters to produce the effective list of type arguments, and compute the | ||
// instantiation cache key from the type IDs of the type arguments. | ||
const typeArguments = map(root.outerTypeParameters, mapper); | ||
const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); | ||
const id = getTypeListId(typeArguments); | ||
let result = root.instantiations!.get(id); | ||
if (!result) { | ||
|
@@ -13874,7 +13902,7 @@ namespace ts { | |
// type A | B, we produce (A extends U ? X : Y) | (B extends U ? X : Y). | ||
if (root.isDistributive) { | ||
const checkType = <TypeParameter>root.checkType; | ||
const instantiatedType = mapper(checkType); | ||
const instantiatedType = getMappedType(checkType, mapper); | ||
if (checkType !== instantiatedType && instantiatedType.flags & (TypeFlags.Union | TypeFlags.Never)) { | ||
return mapType(instantiatedType, t => getConditionalType(root, createReplacementMapper(checkType, t, mapper))); | ||
} | ||
|
@@ -13906,7 +13934,7 @@ namespace ts { | |
function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type { | ||
const flags = type.flags; | ||
if (flags & TypeFlags.TypeParameter) { | ||
return mapper(type); | ||
return getMappedType(type, mapper); | ||
} | ||
if (flags & TypeFlags.Object) { | ||
const objectFlags = (<ObjectType>type).objectFlags; | ||
|
@@ -15705,10 +15733,10 @@ namespace ts { | |
// We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component | ||
const saved = entry & RelationComparisonResult.ReportsMask; | ||
if (saved & RelationComparisonResult.ReportsUnmeasurable) { | ||
instantiateType(source, reportUnmeasurableMarkers); | ||
instantiateType(source, makeFunctionTypeMapper(reportUnmeasurableMarkers)); | ||
} | ||
if (saved & RelationComparisonResult.ReportsUnreliable) { | ||
instantiateType(source, reportUnreliableMarkers); | ||
instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); | ||
} | ||
} | ||
return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; | ||
|
@@ -16153,7 +16181,7 @@ namespace ts { | |
if (modifiersRelated) { | ||
let result: Ternary; | ||
const targetConstraint = getConstraintTypeFromMappedType(target); | ||
const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers); | ||
const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), makeFunctionTypeMapper(getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers)); | ||
if (result = isRelatedTo(targetConstraint, sourceConstraint, reportErrors)) { | ||
const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); | ||
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors); | ||
|
@@ -16632,7 +16660,7 @@ namespace ts { | |
*/ | ||
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { | ||
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, | ||
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers); | ||
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, makeFunctionTypeMapper(reportUnreliableMarkers)); | ||
} | ||
|
||
function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { | ||
|
@@ -17829,8 +17857,8 @@ namespace ts { | |
signature, | ||
flags, | ||
compareTypes, | ||
mapper: t => mapToInferredType(context, t, /*fix*/ true), | ||
nonFixingMapper: t => mapToInferredType(context, t, /*fix*/ false), | ||
mapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ true)), | ||
nonFixingMapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ false)), | ||
}; | ||
return context; | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.