Template literal types and mapped type 'as' clauses (#40336)

* Initial implementation of string template types

* Accept new API baselines

* Accept new baselines

* Unified checking for large cross product union types

* Accept new baselines

* Ensure errors from union type resolution are reported

* Accept new baselines

* Compute constraints for string template types

* Support `as T` clause in mapped types

* Accept new API baselines

* Add missing semicolon

* Add checking of `as T` clauses

* Support casing modifiers in string template types

* Accept new baselines

* Bump keyword maximum length

* fix anders

* Revert "fix anders"

This reverts commit b3178d4618.

* Properly handle 'as T' clause with keyof for mapped type

* Fix lint error

* Single character inferences and anchored end span matching

* Fewer array copy operations in template literal type resolution

* Handle cases where 'as T' maps multiple properties onto one

* Fix lint error

* Store key type instead of type mapper in MappedSymbol

* No constraint on `in T` type when `as N` clause present

* Rename from TemplateType to TemplateLiteralType

* Accept new API baselines

* Add tests

* Accept new baselines

* Address CR feedback

* Accept new API baselines

Co-authored-by: Erich Gamma <egamma@microsoft.com>
This commit is contained in:
Anders Hejlsberg
2020-09-09 17:23:22 -10:00
committed by GitHub
co-authored by Erich Gamma
parent 96b0832cf6
commit 6f0c91c4cb
40 changed files with 3474 additions and 583 deletions
+261 -53
View File
@@ -698,6 +698,7 @@ namespace ts {
const intersectionTypes = new Map<string, Type>();
const literalTypes = new Map<string, LiteralType>();
const indexedAccessTypes = new Map<string, IndexedAccessType>();
const templateLiteralTypes = new Map<string, TemplateLiteralType>();
const substitutionTypes = new Map<string, SubstitutionType>();
const evolvingArrayTypes: EvolvingArrayType[] = [];
const undefinedProperties: SymbolTable = new Map();
@@ -748,6 +749,7 @@ namespace ts {
const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]);
const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType;
const numberOrBigIntType = getUnionType([numberType, bigintType]);
const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType]);
const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(<TypeParameter>t) : t);
const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t);
@@ -4489,6 +4491,19 @@ namespace ts {
const indexTypeNode = typeToTypeNodeHelper(indexedType, context);
return factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode);
}
if (type.flags & TypeFlags.TemplateLiteral) {
const texts = (<TemplateLiteralType>type).texts;
const casings = (<TemplateLiteralType>type).casings;
const types = (<TemplateLiteralType>type).types;
const templateHead = factory.createTemplateHead(texts[0]);
const templateSpans = factory.createNodeArray(
map(types, (t, i) => factory.createTemplateLiteralTypeSpan(
casings[i],
typeToTypeNodeHelper(t, context),
(i < types.length - 1 ? factory.createTemplateMiddle : factory.createTemplateTail)(texts[i + 1]))));
context.approximateLength += 2;
return factory.createTemplateLiteralType(templateHead, templateSpans);
}
if (type.flags & TypeFlags.IndexedAccess) {
const objectTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).objectType, context);
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
@@ -4541,8 +4556,9 @@ namespace ts {
appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context);
}
const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode);
const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined;
const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context);
const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode);
const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode);
context.approximateLength += 10;
return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
}
@@ -10350,9 +10366,6 @@ namespace ts {
// bound includes those keys that are known to always be present, for example because
// because of constraints on type parameters (e.g. 'keyof T' for a constrained T).
function getLowerBoundOfKeyType(type: Type): Type {
if (type.flags & (TypeFlags.Any | TypeFlags.Primitive)) {
return type;
}
if (type.flags & TypeFlags.Index) {
const t = getApparentType((<IndexType>type).type);
return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t);
@@ -10373,7 +10386,7 @@ namespace ts {
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
}
return neverType;
return type;
}
/** Resolve the members of a mapped type { [P in K]: T } */
@@ -10387,6 +10400,7 @@ namespace ts {
// and T as the template type.
const typeParameter = getTypeParameterFromMappedType(type);
const constraintType = getConstraintTypeFromMappedType(type);
const nameType = getNameTypeFromMappedType(<MappedType>type.target || type);
const templateType = getTemplateTypeFromMappedType(<MappedType>type.target || type);
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
const templateModifiers = getMappedTypeModifiers(type);
@@ -10408,22 +10422,23 @@ namespace ts {
}
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
function addMemberForKeyType(t: Type) {
// 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 = appendTypeMapping(type.mapper, typeParameter, t);
function addMemberForKeyType(keyType: Type) {
const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType;
forEachType(propNameType, t => addMemberForKeyTypeWorker(keyType, t));
}
function addMemberForKeyTypeWorker(keyType: Type, propNameType: Type) {
// 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)) {
const propName = getPropertyNameFromType(t);
if (isTypeUsableAsPropertyName(propNameType)) {
const propName = getPropertyNameFromType(propNameType);
// String enum members from separate enums with identical values
// are distinct types with the same property name. Make the resulting
// property symbol's name type be the union of those enum member types.
const existingProp = members.get(propName) as MappedSymbol | undefined;
if (existingProp) {
existingProp.nameType = getUnionType([existingProp.nameType!, t]);
existingProp.mapper = appendTypeMapping(type.mapper, typeParameter, existingProp.nameType);
existingProp.nameType = getUnionType([existingProp.nameType!, propNameType]);
existingProp.keyType = getUnionType([existingProp.keyType, keyType]);
}
else {
const modifiersProp = getPropertyOfType(modifiersType, propName);
@@ -10435,19 +10450,20 @@ namespace ts {
const prop = <MappedSymbol>createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName,
CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0));
prop.mappedType = type;
prop.nameType = propNameType;
prop.keyType = keyType;
if (modifiersProp) {
prop.syntheticOrigin = modifiersProp;
prop.declarations = modifiersProp.declarations;
}
prop.nameType = t;
prop.mapper = templateMapper;
members.set(propName, prop);
}
}
else if (t.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.Enum)) {
const propType = instantiateType(templateType, templateMapper);
if (t.flags & (TypeFlags.Any | TypeFlags.String)) {
stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
else if (propNameType.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.Enum)) {
const propType = instantiateType(templateType, appendTypeMapping(type.mapper, typeParameter, keyType));
if (propNameType.flags & (TypeFlags.Any | TypeFlags.String)) {
stringIndexInfo = createIndexInfo(stringIndexInfo ? getUnionType([stringIndexInfo.type, propType]) : propType,
!!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
}
else {
numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType,
@@ -10459,12 +10475,14 @@ namespace ts {
function getTypeOfMappedSymbol(symbol: MappedSymbol) {
if (!symbol.type) {
const mappedType = symbol.mappedType;
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
symbol.mappedType.containsError = true;
mappedType.containsError = true;
return errorType;
}
const templateType = getTemplateTypeFromMappedType(<MappedType>symbol.mappedType.target || symbol.mappedType);
const propType = instantiateType(templateType, symbol.mapper);
const templateType = getTemplateTypeFromMappedType(<MappedType>mappedType.target || mappedType);
const mapper = appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), symbol.keyType);
const propType = instantiateType(templateType, mapper);
// When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the
// type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks
// mode, if the underlying property is optional we remove 'undefined' from the type.
@@ -10472,11 +10490,10 @@ namespace ts {
symbol.checkFlags & CheckFlags.StripOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
propType;
if (!popTypeResolution()) {
error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(symbol.mappedType));
error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType));
type = errorType;
}
symbol.type = type;
symbol.mapper = undefined!;
}
return symbol.type;
}
@@ -10491,6 +10508,12 @@ namespace ts {
(type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType);
}
function getNameTypeFromMappedType(type: MappedType) {
return type.declaration.nameType ?
type.nameType || (type.nameType = instantiateType(getTypeFromTypeNode(type.declaration.nameType), type.mapper)) :
undefined;
}
function getTemplateTypeFromMappedType(type: MappedType) {
return type.templateType ||
(type.templateType = type.declaration.type ?
@@ -10784,7 +10807,7 @@ namespace ts {
}
function getBaseConstraintOfType(type: Type): Type | undefined {
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection)) {
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) {
const constraint = getResolvedBaseConstraint(<InstantiableType | UnionOrIntersectionType>type);
return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined;
}
@@ -10878,6 +10901,11 @@ namespace ts {
if (t.flags & TypeFlags.Index) {
return keyofConstraintType;
}
if (t.flags & TypeFlags.TemplateLiteral) {
const types = (<TemplateLiteralType>t).types;
const constraints = mapDefined(types, getBaseConstraint);
return constraints.length === types.length ? getTemplateLiteralType((<TemplateLiteralType>t).texts, (<TemplateLiteralType>t).casings, constraints) : stringType;
}
if (t.flags & TypeFlags.IndexedAccess) {
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
@@ -10955,7 +10983,7 @@ namespace ts {
function getResolvedApparentTypeOfMappedType(type: MappedType) {
const typeVariable = getHomomorphicTypeVariable(type);
if (typeVariable) {
if (typeVariable && !type.declaration.nameType) {
const constraint = getConstraintOfTypeParameter(typeVariable);
if (constraint && (isArrayType(constraint) || isTupleType(constraint))) {
return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper));
@@ -11895,6 +11923,11 @@ namespace ts {
grandParent.kind === SyntaxKind.NamedTupleMember && (<NamedTupleMember>grandParent).dotDotDotToken) {
inferences = append(inferences, createArrayType(unknownType));
}
// When an 'infer T' declaration is immediately contained in a string template type, we infer a 'string'
// constraint.
else if (grandParent.kind === SyntaxKind.TemplateLiteralTypeSpan) {
inferences = append(inferences, stringType);
}
}
}
}
@@ -12720,7 +12753,9 @@ namespace ts {
// Transform [A, ...(X | Y | Z)] into [A, ...X] | [A, ...Y] | [A, ...Z]
const unionIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic && t.flags & (TypeFlags.Never | TypeFlags.Union)));
if (unionIndex >= 0) {
return mapType(elementTypes[unionIndex], t => createNormalizedTupleType(target, replaceElement(elementTypes, unionIndex, t)));
return checkCrossProductUnion(map(elementTypes, (t, i) => target.elementFlags[i] & ElementFlags.Variadic ? t : unknownType)) ?
mapType(elementTypes[unionIndex], t => createNormalizedTupleType(target, replaceElement(elementTypes, unionIndex, t))) :
errorType;
}
// If there are no variadic elements with non-generic types, just create a type reference with the same target type.
const spreadIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic) && !(t.flags & TypeFlags.InstantiableNonPrimitive) && !isGenericMappedType(t));
@@ -13257,9 +13292,7 @@ namespace ts {
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
// If the estimated size of the resulting union type exceeds 100000 constituents, report an error.
const size = reduceLeft(typeSet, (n, t) => n * (t.flags & TypeFlags.Union ? (<UnionType>t).types.length : 1), 1);
if (size >= 100000) {
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
if (!checkCrossProductUnion(typeSet)) {
return errorType;
}
const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0);
@@ -13276,6 +13309,15 @@ namespace ts {
return result;
}
function checkCrossProductUnion(types: readonly Type[]) {
const size = reduceLeft(types, (n, t) => n * (t.flags & TypeFlags.Union ? (<UnionType>t).types.length : t.flags & TypeFlags.Never ? 0 : 1), 1);
if (size >= 100000) {
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
return false;
}
return true;
}
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
@@ -13299,6 +13341,13 @@ namespace ts {
type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false));
}
function getIndexTypeForMappedType(type: MappedType, noIndexSignatures: boolean | undefined) {
const constraint = filterType(getConstraintTypeFromMappedType(type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String)));
return type.declaration.nameType ?
instantiateType(getTypeFromTypeNode(type.declaration.nameType), appendTypeMapping(type.mapper, getTypeParameterFromMappedType(type), constraint)) :
constraint;
}
function getLiteralTypeFromPropertyName(name: PropertyName) {
if (isPrivateIdentifier(name)) {
return neverType;
@@ -13347,7 +13396,7 @@ namespace ts {
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
getObjectFlags(type) & ObjectFlags.Mapped ? filterType(getConstraintTypeFromMappedType(<MappedType>type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))) :
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(<MappedType>type, noIndexSignatures) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
@@ -13392,6 +13441,83 @@ namespace ts {
return links.resolvedType;
}
function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getTemplateLiteralType(
[node.head.text, ...map(node.templateSpans, span => span.literal.text)],
map(node.templateSpans, span => span.casing),
map(node.templateSpans, span => getTypeFromTypeNode(span.type)));
}
return links.resolvedType;
}
function getTemplateLiteralType(texts: readonly string[], casings: readonly TemplateCasing[], types: readonly Type[]): Type {
const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union)));
if (unionIndex >= 0) {
return checkCrossProductUnion(types) ?
mapType(types[unionIndex], t => getTemplateLiteralType(texts, casings, replaceElement(types, unionIndex, t))) :
errorType;
}
const newTypes = [];
const newCasings = [];
const newTexts = [];
let text = texts[0];
for (let i = 0; i < types.length; i++) {
const t = types[i];
if (t.flags & TypeFlags.Literal) {
const s = applyTemplateCasing(getTemplateStringForType(t) || "", casings[i]);
text += s;
text += texts[i + 1];
}
else if (isGenericIndexType(t)) {
newTypes.push(t);
newCasings.push(casings[i]);
newTexts.push(text);
text = texts[i + 1];
}
else {
return stringType;
}
}
if (newTypes.length === 0) {
return getLiteralType(text);
}
newTexts.push(text);
const id = `${getTypeListId(newTypes)}|${newCasings.join(",")}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`;
let type = templateLiteralTypes.get(id);
if (!type) {
templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newCasings, newTypes));
}
return type;
}
function getTemplateStringForType(type: Type) {
return type.flags & TypeFlags.StringLiteral ? (<StringLiteralType>type).value :
type.flags & TypeFlags.NumberLiteral ? "" + (<NumberLiteralType>type).value :
type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((<BigIntLiteralType>type).value) :
type.flags & TypeFlags.BooleanLiteral ? (<IntrinsicType>type).intrinsicName :
undefined;
}
function applyTemplateCasing(str: string, casing: TemplateCasing) {
switch (casing) {
case TemplateCasing.Uppercase: return str.toUpperCase();
case TemplateCasing.Lowercase: return str.toLowerCase();
case TemplateCasing.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
case TemplateCasing.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
}
return str;
}
function createTemplateLiteralType(texts: readonly string[], casings: readonly TemplateCasing[], types: readonly Type[]) {
const type = <TemplateLiteralType>createType(TypeFlags.TemplateLiteral);
type.texts = texts;
type.casings = casings;
type.types = types;
return type;
}
function createIndexedAccessType(objectType: Type, indexType: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) {
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
type.objectType = objectType;
@@ -13624,7 +13750,7 @@ namespace ts {
}
return !!((<UnionOrIntersectionType>type).objectFlags & ObjectFlags.IsGenericIndexType);
}
return !!(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index));
return !!(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral));
}
function isThisTypeParameter(type: Type): boolean {
@@ -14122,7 +14248,7 @@ namespace ts {
}
function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) {
return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index));
return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral));
}
function isSinglePropertyAnonymousObjectType(type: Type) {
@@ -14208,7 +14334,7 @@ namespace ts {
}
return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly));
}
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral)) {
return left;
}
@@ -14540,6 +14666,8 @@ namespace ts {
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node);
case SyntaxKind.InferType:
return getTypeFromInferTypeNode(<InferTypeNode>node);
case SyntaxKind.TemplateLiteralType:
return getTypeFromTemplateTypeNode(<TemplateLiteralTypeNode>node);
case SyntaxKind.ImportType:
return getTypeFromImportTypeNode(<ImportTypeNode>node);
// This function assumes that an identifier, qualified name, or property access expression is a type expression
@@ -14837,13 +14965,18 @@ namespace ts {
if (typeVariable !== mappedTypeVariable) {
return mapType(getReducedType(mappedTypeVariable), t => {
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) {
if (isGenericTupleType(t)) {
return instantiateMappedGenericTupleType(t, type, typeVariable, mapper);
if (!type.declaration.nameType) {
if (isArrayType(t)) {
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
}
if (isGenericTupleType(t)) {
return instantiateMappedGenericTupleType(t, type, typeVariable, mapper);
}
if (isTupleType(t)) {
return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper));
}
}
const replacementMapper = prependTypeMapping(typeVariable, t, mapper);
return isArrayType(t) ? instantiateMappedArrayType(t, type, replacementMapper) :
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
instantiateAnonymousType(type, replacementMapper);
return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper));
}
return t;
});
@@ -15001,6 +15134,9 @@ namespace ts {
if (flags & TypeFlags.Index) {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
}
if (flags & TypeFlags.TemplateLiteral) {
return getTemplateLiteralType((<TemplateLiteralType>type).texts, (<TemplateLiteralType>type).casings, instantiateTypes((<TemplateLiteralType>type).types, mapper));
}
if (flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper), /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
@@ -16987,7 +17123,8 @@ namespace ts {
if (target.flags & TypeFlags.TypeParameter) {
// A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q].
if (getObjectFlags(source) & ObjectFlags.Mapped && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(<MappedType>source))) {
if (getObjectFlags(source) & ObjectFlags.Mapped && !(<MappedType>source).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(<MappedType>source))) {
if (!(getMappedTypeModifiers(<MappedType>source) & MappedTypeModifiers.IncludeOptional)) {
const templateType = getTemplateTypeFromMappedType(<MappedType>source);
const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(<MappedType>source));
@@ -17044,7 +17181,7 @@ namespace ts {
}
}
}
else if (isGenericMappedType(target)) {
else if (isGenericMappedType(target) && !target.declaration.nameType) {
// A source type T is related to a target type { [P in X]: T[P] }
const template = getTemplateTypeFromMappedType(target);
const modifiers = getMappedTypeModifiers(target);
@@ -17126,6 +17263,13 @@ namespace ts {
return result;
}
}
else if (source.flags & TypeFlags.TemplateLiteral) {
const constraint = getBaseConstraintOfType(source);
if (constraint && (result = isRelatedTo(constraint, target, reportErrors))) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
else if (source.flags & TypeFlags.Conditional) {
if (target.flags & TypeFlags.Conditional) {
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
@@ -17339,7 +17483,9 @@ namespace ts {
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);
if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) {
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors);
}
}
}
return Ternary.False;
@@ -19575,6 +19721,9 @@ namespace ts {
inferFromTypes(sourceType, target);
}
}
else if (target.flags & TypeFlags.TemplateLiteral) {
inferToTemplateLiteralType(source, <TemplateLiteralType>target);
}
else {
source = getReducedType(source);
if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) {
@@ -19627,8 +19776,8 @@ namespace ts {
// We stop inferring and report a circularity if we encounter duplicate recursion identities on both
// the source side and the target side.
const saveExpandingFlags = expandingFlags;
const sourceIdentity = getRecursionIdentity(source);
const targetIdentity = getRecursionIdentity(target);
const sourceIdentity = getRecursionIdentity(source) || source;
const targetIdentity = getRecursionIdentity(target) || target;
if (sourceIdentity && contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source;
if (targetIdentity && contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target;
if (expandingFlags !== ExpandingFlags.Both) {
@@ -19853,6 +20002,39 @@ namespace ts {
}
}
function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) {
if (source.flags & (TypeFlags.StringLike | TypeFlags.Index)) {
const matches = source.flags & TypeFlags.StringLiteral ? inferLiteralsFromTemplateLiteralType(<StringLiteralType>source, target) :
source.flags & TypeFlags.TemplateLiteral && arraysEqual((<TemplateLiteralType>source).texts, target.texts) && arraysEqual((<TemplateLiteralType>source).casings, target.casings)? (<TemplateLiteralType>source).types :
undefined;
const types = target.types;
for (let i = 0; i < types.length; i++) {
inferFromTypes(matches ? matches[i] : source.flags & TypeFlags.StringLiteral ? neverType : stringType, types[i]);
}
}
}
function inferLiteralsFromTemplateLiteralType(source: StringLiteralType, target: TemplateLiteralType): Type[] | undefined {
const value = source.value;
const texts = target.texts;
const lastIndex = texts.length - 1;
const startText = texts[0];
const endText = texts[lastIndex];
if (!(value.startsWith(startText) && value.endsWith(endText))) return undefined;
const matches = [];
const str = value.slice(startText.length, value.length - endText.length);
let pos = 0;
for (let i = 1; i < lastIndex; i++) {
const delim = texts[i];
const delimPos = delim.length > 0 ? str.indexOf(delim, pos) : pos < str.length ? pos + 1 : -1;
if (delimPos < 0) return undefined;
matches.push(getLiteralType(str.slice(pos, delimPos)));
pos = delimPos + delim.length;
}
matches.push(getLiteralType(str.slice(pos)));
return matches;
}
function inferFromObjectTypes(source: Type, target: Type) {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
@@ -19865,8 +20047,11 @@ namespace ts {
// from S to T and from X to Y.
inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target));
inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target));
const sourceNameType = getNameTypeFromMappedType(source);
const targetNameType = getNameTypeFromMappedType(target);
if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType);
}
if (getObjectFlags(target) & ObjectFlags.Mapped) {
if (getObjectFlags(target) & ObjectFlags.Mapped && !(<MappedType>target).declaration.nameType) {
const constraintType = getConstraintTypeFromMappedType(<MappedType>target);
if (inferToMappedType(source, <MappedType>target, constraintType)) {
return;
@@ -20015,7 +20200,7 @@ namespace ts {
function hasPrimitiveConstraint(type: TypeParameter): boolean {
const constraint = getConstraintOfTypeParameter(type);
return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index);
return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral);
}
function isObjectLiteralType(type: Type) {
@@ -26059,7 +26244,7 @@ namespace ts {
else {
const contextualType = getIndexedAccessType(restType, getLiteralType(i - index));
const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode);
const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index);
const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral);
types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType));
flags.push(ElementFlags.Required);
}
@@ -29098,7 +29283,7 @@ namespace ts {
// and the right operand to be of type Any, an object type, or a type parameter type.
// The result is always of the Boolean primitive type.
if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) ||
isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TypeParameter))) {
isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.TypeParameter))) {
error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol);
}
if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) {
@@ -30047,7 +30232,7 @@ namespace ts {
}
// If the contextual type is a literal of a particular primitive type, we consider this a
// literal context for all literals of that primitive type.
return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) ||
contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
@@ -31206,10 +31391,12 @@ namespace ts {
}
}
forEach(node.elements, checkSourceElement);
getTypeFromTypeNode(node);
}
function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) {
forEach(node.types, checkSourceElement);
getTypeFromTypeNode(node);
}
function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) {
@@ -31254,6 +31441,7 @@ namespace ts {
function checkMappedType(node: MappedTypeNode) {
checkSourceElement(node.typeParameter);
checkSourceElement(node.nameType);
checkSourceElement(node.type);
if (!node.type) {
@@ -31261,8 +31449,14 @@ namespace ts {
}
const type = <MappedType>getTypeFromMappedTypeNode(node);
const constraintType = getConstraintTypeFromMappedType(type);
checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter));
const nameType = getNameTypeFromMappedType(type);
if (nameType) {
checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType);
}
else {
const constraintType = getConstraintTypeFromMappedType(type);
checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter));
}
}
function checkThisType(node: ThisTypeNode) {
@@ -31286,6 +31480,18 @@ namespace ts {
registerForUnusedIdentifiersCheck(node);
}
function checkTemplateType(node: TemplateLiteralTypeNode) {
forEachChild(node, checkSourceElement);
getTypeFromTypeNode(node);
for (const span of node.templateSpans) {
const type = getTypeFromTypeNode(span.type);
checkTypeAssignableTo(type, templateConstraintType, span.type);
if (!everyType(type, t => !!(t.flags & TypeFlags.Literal) || isGenericIndexType(t))) {
error(span.type, Diagnostics.Template_literal_type_argument_0_is_not_literal_type_or_a_generic_type, typeToString(type));
}
}
}
function checkImportType(node: ImportTypeNode) {
checkSourceElement(node.argument);
getTypeFromTypeNode(node);
@@ -35897,6 +36103,8 @@ namespace ts {
return checkConditionalType(<ConditionalTypeNode>node);
case SyntaxKind.InferType:
return checkInferType(<InferTypeNode>node);
case SyntaxKind.TemplateLiteralType:
return checkTemplateType(<TemplateLiteralTypeNode>node);
case SyntaxKind.ImportType:
return checkImportType(<ImportTypeNode>node);
case SyntaxKind.NamedTupleMember:
+4
View File
@@ -3028,6 +3028,10 @@
"category": "Error",
"code": 2792
},
"Template literal type argument '{0}' is not literal type or a generic type.": {
"category": "Error",
"code": 2793
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
+29
View File
@@ -1313,6 +1313,8 @@ namespace ts {
return emitConstructSignature(<ConstructSignatureDeclaration>node);
case SyntaxKind.IndexSignature:
return emitIndexSignature(<IndexSignatureDeclaration>node);
case SyntaxKind.TemplateLiteralTypeSpan:
return emitTemplateTypeSpan(<TemplateLiteralTypeSpan>node);
// Types
case SyntaxKind.TypePredicate:
@@ -1357,6 +1359,8 @@ namespace ts {
return emitMappedType(<MappedTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
case SyntaxKind.TemplateLiteralType:
return emitTemplateType(<TemplateLiteralTypeNode>node);
case SyntaxKind.ImportType:
return emitImportTypeNode(<ImportTypeNode>node);
case SyntaxKind.JSDocAllType:
@@ -2010,6 +2014,20 @@ namespace ts {
writeTrailingSemicolon();
}
function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) {
const keyword = node.casing === TemplateCasing.Uppercase ? "uppercase" :
node.casing === TemplateCasing.Lowercase ? "lowercase" :
node.casing === TemplateCasing.Capitalize ? "capitalize" :
node.casing === TemplateCasing.Uncapitalize ? "uncapitalize" :
undefined;
if (keyword) {
writeKeyword(keyword);
writeSpace();
}
emit(node.type);
emit(node.literal);
}
function emitSemicolonClassElement() {
writeTrailingSemicolon();
}
@@ -2202,6 +2220,12 @@ namespace ts {
writePunctuation("[");
pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter);
if (node.nameType) {
writeSpace();
writeKeyword("as");
writeSpace();
emit(node.nameType);
}
writePunctuation("]");
if (node.questionToken) {
@@ -2228,6 +2252,11 @@ namespace ts {
emitExpression(node.literal);
}
function emitTemplateType(node: TemplateLiteralTypeNode) {
emit(node.head);
emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans);
}
function emitImportTypeNode(node: ImportTypeNode) {
if (node.isTypeOf) {
writeKeyword("typeof");
+45 -3
View File
@@ -94,6 +94,8 @@ namespace ts {
updateConstructSignature,
createIndexSignature,
updateIndexSignature,
createTemplateLiteralTypeSpan,
updateTemplateLiteralTypeSpan,
createKeywordTypeNode,
createTypePredicateNode,
updateTypePredicateNode,
@@ -138,6 +140,8 @@ namespace ts {
updateMappedTypeNode,
createLiteralTypeNode,
updateLiteralTypeNode,
createTemplateLiteralType,
updateTemplateLiteralType,
createObjectBindingPattern,
updateObjectBindingPattern,
createArrayBindingPattern,
@@ -1600,6 +1604,25 @@ namespace ts {
: node;
}
// @api
function createTemplateLiteralTypeSpan(casing: TemplateCasing, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
const node = createBaseNode<TemplateLiteralTypeSpan>(SyntaxKind.TemplateLiteralTypeSpan);
node.casing = casing;
node.type = type;
node.literal = literal;
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}
// @api
function updateTemplateLiteralTypeSpan(casing: TemplateCasing, node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
return node.casing !== casing
|| node.type !== type
|| node.literal !== literal
? update(createTemplateLiteralTypeSpan(casing, type, literal), node)
: node;
}
//
// Types
//
@@ -1891,6 +1914,23 @@ namespace ts {
: node;
}
// @api
function createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) {
const node = createBaseNode<TemplateLiteralTypeNode>(SyntaxKind.TemplateLiteralType);
node.head = head;
node.templateSpans = createNodeArray(templateSpans);
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}
// @api
function updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) {
return node.head !== head
|| node.templateSpans !== templateSpans
? update(createTemplateLiteralType(head, templateSpans), node)
: node;
}
// @api
function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf = false) {
const node = createBaseNode<ImportTypeNode>(SyntaxKind.ImportType);
@@ -1968,10 +2008,11 @@ namespace ts {
}
// @api
function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
const node = createBaseNode<MappedTypeNode>(SyntaxKind.MappedType);
node.readonlyToken = readonlyToken;
node.typeParameter = typeParameter;
node.nameType = nameType;
node.questionToken = questionToken;
node.type = type;
node.transformFlags = TransformFlags.ContainsTypeScript;
@@ -1979,12 +2020,13 @@ namespace ts {
}
// @api
function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
return node.readonlyToken !== readonlyToken
|| node.typeParameter !== typeParameter
|| node.nameType !== nameType
|| node.questionToken !== questionToken
|| node.type !== type
? update(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node)
? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), node)
: node;
}
+54 -1
View File
@@ -206,6 +206,7 @@ namespace ts {
case SyntaxKind.MappedType:
return visitNode(cbNode, (<MappedTypeNode>node).readonlyToken) ||
visitNode(cbNode, (<MappedTypeNode>node).typeParameter) ||
visitNode(cbNode, (<MappedTypeNode>node).nameType) ||
visitNode(cbNode, (<MappedTypeNode>node).questionToken) ||
visitNode(cbNode, (<MappedTypeNode>node).type);
case SyntaxKind.LiteralType:
@@ -424,6 +425,10 @@ namespace ts {
return visitNode(cbNode, (<TemplateExpression>node).head) || visitNodes(cbNode, cbNodes, (<TemplateExpression>node).templateSpans);
case SyntaxKind.TemplateSpan:
return visitNode(cbNode, (<TemplateSpan>node).expression) || visitNode(cbNode, (<TemplateSpan>node).literal);
case SyntaxKind.TemplateLiteralType:
return visitNode(cbNode, (<TemplateLiteralTypeNode>node).head) || visitNodes(cbNode, cbNodes, (<TemplateLiteralTypeNode>node).templateSpans);
case SyntaxKind.TemplateLiteralTypeSpan:
return visitNode(cbNode, (<TemplateLiteralTypeSpan>node).type) || visitNode(cbNode, (<TemplateLiteralTypeSpan>node).literal);
case SyntaxKind.ComputedPropertyName:
return visitNode(cbNode, (<ComputedPropertyName>node).expression);
case SyntaxKind.HeritageClause:
@@ -2584,6 +2589,49 @@ namespace ts {
);
}
function parseTemplateType(): TemplateLiteralTypeNode {
const pos = getNodePos();
return finishNode(
factory.createTemplateLiteralType(
parseTemplateHead(/*isTaggedTemplate*/ false),
parseTemplateTypeSpans()
),
pos
);
}
function parseTemplateTypeSpans() {
const pos = getNodePos();
const list = [];
let node: TemplateLiteralTypeSpan;
do {
node = parseTemplateTypeSpan();
list.push(node);
}
while (node.literal.kind === SyntaxKind.TemplateMiddle);
return createNodeArray(list, pos);
}
function parseTemplateTypeSpan(): TemplateLiteralTypeSpan {
const pos = getNodePos();
return finishNode(
factory.createTemplateLiteralTypeSpan(
parseTemplateCasing(),
parseType(),
parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false)
),
pos
);
}
function parseTemplateCasing(): TemplateCasing {
return parseOptional(SyntaxKind.UppercaseKeyword) ? TemplateCasing.Uppercase :
parseOptional(SyntaxKind.LowercaseKeyword) ? TemplateCasing.Lowercase :
parseOptional(SyntaxKind.CapitalizeKeyword) ? TemplateCasing.Capitalize :
parseOptional(SyntaxKind.UncapitalizeKeyword) ? TemplateCasing.Uncapitalize :
TemplateCasing.None;
}
function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) {
if (token() === SyntaxKind.CloseBraceToken) {
reScanTemplateToken(isTaggedTemplate);
@@ -3252,6 +3300,7 @@ namespace ts {
}
parseExpected(SyntaxKind.OpenBracketToken);
const typeParameter = parseMappedTypeParameter();
const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined;
parseExpected(SyntaxKind.CloseBracketToken);
let questionToken: QuestionToken | PlusToken | MinusToken | undefined;
if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) {
@@ -3263,7 +3312,7 @@ namespace ts {
const type = parseTypeAnnotation();
parseSemicolon();
parseExpected(SyntaxKind.CloseBraceToken);
return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), pos);
return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), pos);
}
function parseTupleElementType() {
@@ -3444,6 +3493,8 @@ namespace ts {
return parseImportType();
case SyntaxKind.AssertsKeyword:
return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference();
case SyntaxKind.TemplateHead:
return parseTemplateType();
default:
return parseTypeReference();
}
@@ -3485,6 +3536,8 @@ namespace ts {
case SyntaxKind.InferKeyword:
case SyntaxKind.ImportKeyword:
case SyntaxKind.AssertsKeyword:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
return true;
case SyntaxKind.FunctionKeyword:
return !inStartOfParameter;
+6 -2
View File
@@ -151,6 +151,10 @@ namespace ts {
yield: SyntaxKind.YieldKeyword,
async: SyntaxKind.AsyncKeyword,
await: SyntaxKind.AwaitKeyword,
uppercase: SyntaxKind.UppercaseKeyword,
lowercase: SyntaxKind.LowercaseKeyword,
capitalize: SyntaxKind.CapitalizeKeyword,
uncapitalize: SyntaxKind.UncapitalizeKeyword,
of: SyntaxKind.OfKeyword,
};
@@ -1508,9 +1512,9 @@ namespace ts {
}
function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind {
// Reserved words are between 2 and 11 characters long and start with a lowercase letter
// Reserved words are between 2 and 12 characters long and start with a lowercase letter
const len = tokenValue.length;
if (len >= 2 && len <= 11) {
if (len >= 2 && len <= 12) {
const ch = tokenValue.charCodeAt(0);
if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) {
const keyword = textToKeyword.get(tokenValue);
+56 -9
View File
@@ -186,6 +186,10 @@ namespace ts {
FromKeyword,
GlobalKeyword,
BigIntKeyword,
UppercaseKeyword,
LowercaseKeyword,
CapitalizeKeyword,
UncapitalizeKeyword,
OfKeyword, // LastKeyword and LastToken and LastContextualKeyword
// Parse tree nodes
@@ -230,6 +234,8 @@ namespace ts {
MappedType,
LiteralType,
NamedTupleMember,
TemplateLiteralType,
TemplateLiteralTypeSpan,
ImportType,
// Binding patterns
ObjectBindingPattern,
@@ -538,6 +544,7 @@ namespace ts {
| SyntaxKind.BigIntKeyword
| SyntaxKind.BooleanKeyword
| SyntaxKind.BreakKeyword
| SyntaxKind.CapitalizeKeyword
| SyntaxKind.CaseKeyword
| SyntaxKind.CatchKeyword
| SyntaxKind.ClassKeyword
@@ -570,6 +577,7 @@ namespace ts {
| SyntaxKind.IsKeyword
| SyntaxKind.KeyOfKeyword
| SyntaxKind.LetKeyword
| SyntaxKind.LowercaseKeyword
| SyntaxKind.ModuleKeyword
| SyntaxKind.NamespaceKeyword
| SyntaxKind.NeverKeyword
@@ -597,9 +605,11 @@ namespace ts {
| SyntaxKind.TryKeyword
| SyntaxKind.TypeKeyword
| SyntaxKind.TypeOfKeyword
| SyntaxKind.UncapitalizeKeyword
| SyntaxKind.UndefinedKeyword
| SyntaxKind.UniqueKeyword
| SyntaxKind.UnknownKeyword
| SyntaxKind.UppercaseKeyword
| SyntaxKind.VarKeyword
| SyntaxKind.VoidKeyword
| SyntaxKind.WhileKeyword
@@ -659,6 +669,8 @@ namespace ts {
| SyntaxKind.IndexedAccessType
| SyntaxKind.MappedType
| SyntaxKind.LiteralType
| SyntaxKind.TemplateLiteralType
| SyntaxKind.TemplateLiteralTypeSpan
| SyntaxKind.ImportType
| SyntaxKind.ExpressionWithTypeArguments
| SyntaxKind.JSDocTypeExpression
@@ -1622,6 +1634,7 @@ namespace ts {
readonly kind: SyntaxKind.MappedType;
readonly readonlyToken?: ReadonlyToken | PlusToken | MinusToken;
readonly typeParameter: TypeParameterDeclaration;
readonly nameType?: TypeNode;
readonly questionToken?: QuestionToken | PlusToken | MinusToken;
readonly type?: TypeNode;
}
@@ -1641,6 +1654,28 @@ namespace ts {
export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral;
export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral;
export interface TemplateLiteralTypeNode extends TypeNode {
kind: SyntaxKind.TemplateLiteralType,
readonly head: TemplateHead;
readonly templateSpans: NodeArray<TemplateLiteralTypeSpan>;
}
export interface TemplateLiteralTypeSpan extends TypeNode {
readonly kind: SyntaxKind.TemplateLiteralTypeSpan,
readonly parent: TemplateLiteralTypeNode;
readonly casing: TemplateCasing;
readonly type: TypeNode;
readonly literal: TemplateMiddle | TemplateTail;
}
export const enum TemplateCasing {
None,
Uppercase,
Lowercase,
Capitalize,
Uncapitalize,
}
// Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing.
// Consider 'Expression'. Without the brand, 'Expression' is actually no different
// (structurally) than 'Node'. Because of this you can pass any Node to a function that
@@ -2108,21 +2143,21 @@ namespace ts {
export interface TemplateHead extends TemplateLiteralLikeNode {
readonly kind: SyntaxKind.TemplateHead;
readonly parent: TemplateExpression;
readonly parent: TemplateExpression | TemplateLiteralTypeNode;
/* @internal */
templateFlags?: TokenFlags;
}
export interface TemplateMiddle extends TemplateLiteralLikeNode {
readonly kind: SyntaxKind.TemplateMiddle;
readonly parent: TemplateSpan;
readonly parent: TemplateSpan | TemplateLiteralTypeSpan;
/* @internal */
templateFlags?: TokenFlags;
}
export interface TemplateTail extends TemplateLiteralLikeNode {
readonly kind: SyntaxKind.TemplateTail;
readonly parent: TemplateSpan;
readonly parent: TemplateSpan | TemplateLiteralTypeSpan;
/* @internal */
templateFlags?: TokenFlags;
}
@@ -4707,7 +4742,7 @@ namespace ts {
/* @internal */
export interface MappedSymbol extends TransientSymbol {
mappedType: MappedType;
mapper: TypeMapper;
keyType: Type;
}
/* @internal */
@@ -4849,6 +4884,7 @@ namespace ts {
Conditional = 1 << 24, // T extends U ? X : Y
Substitution = 1 << 25, // Type parameter substitution
NonPrimitive = 1 << 26, // intrinsic object type
TemplateLiteral = 1 << 27, // Template literal type
/* @internal */
AnyOrUnknown = Any | Unknown,
@@ -4866,7 +4902,7 @@ namespace ts {
Intrinsic = Any | Unknown | String | Number | BigInt | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
/* @internal */
Primitive = String | Number | BigInt | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal | UniqueESSymbol,
StringLike = String | StringLiteral,
StringLike = String | StringLiteral | TemplateLiteral,
NumberLike = Number | NumberLiteral | Enum,
BigIntLike = BigInt | BigIntLiteral,
BooleanLike = Boolean | BooleanLiteral,
@@ -4879,7 +4915,7 @@ namespace ts {
StructuredType = Object | Union | Intersection,
TypeVariable = TypeParameter | IndexedAccess,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
InstantiablePrimitive = Index,
InstantiablePrimitive = Index | TemplateLiteral,
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
StructuredOrInstantiable = StructuredType | Instantiable,
/* @internal */
@@ -4887,7 +4923,7 @@ namespace ts {
/* @internal */
Simplifiable = IndexedAccess | Conditional,
/* @internal */
Substructure = Object | Union | Intersection | Index | IndexedAccess | Conditional | Substitution,
Substructure = Object | Union | Intersection | Index | IndexedAccess | Conditional | Substitution | TemplateLiteral,
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
@@ -5194,6 +5230,7 @@ namespace ts {
declaration: MappedTypeNode;
typeParameter?: TypeParameter;
constraintType?: Type;
nameType?: Type;
templateType?: Type;
modifiersType?: Type;
resolvedApparentType?: Type;
@@ -5334,6 +5371,12 @@ namespace ts {
combinedMapper?: TypeMapper;
}
export interface TemplateLiteralType extends InstantiableType {
texts: readonly string[]; // Always one element longer than casings/types
casings: readonly TemplateCasing[]; // Always at least one element
types: readonly Type[]; // Always at least one element
}
// Type parameter substitution (TypeFlags.Substitution)
// Substitution types are created for type parameters or indexed access types that occur in the
// true branch of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the
@@ -6715,6 +6758,8 @@ namespace ts {
createIndexSignature(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): IndexSignatureDeclaration;
/* @internal */ createIndexSignature(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): IndexSignatureDeclaration; // eslint-disable-line @typescript-eslint/unified-signatures
updateIndexSignature(node: IndexSignatureDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): IndexSignatureDeclaration;
createTemplateLiteralTypeSpan(casing: TemplateCasing, type: TypeNode, literal: TemplateMiddle | TemplateTail): TemplateLiteralTypeSpan;
updateTemplateLiteralTypeSpan(casing: TemplateCasing, node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail): TemplateLiteralTypeSpan;
//
// Types
@@ -6760,10 +6805,12 @@ namespace ts {
updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode): TypeOperatorNode;
createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode;
updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode;
createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode;
updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode;
createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode;
updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode;
createLiteralTypeNode(literal: LiteralTypeNode["literal"]): LiteralTypeNode;
updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]): LiteralTypeNode;
createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]): TemplateLiteralTypeNode;
updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]): TemplateLiteralTypeNode;
//
// Binding Patterns
+1
View File
@@ -565,6 +565,7 @@ namespace ts {
return factory.updateMappedTypeNode((<MappedTypeNode>node),
nodeVisitor((<MappedTypeNode>node).readonlyToken, tokenVisitor, isToken),
nodeVisitor((<MappedTypeNode>node).typeParameter, visitor, isTypeParameterDeclaration),
nodeVisitor((<MappedTypeNode>node).nameType, visitor, isTypeNode),
nodeVisitor((<MappedTypeNode>node).questionToken, tokenVisitor, isToken),
nodeVisitor((<MappedTypeNode>node).type, visitor, isTypeNode));
@@ -48,6 +48,6 @@ namespace ts.codefix {
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { container, typeNode, constraint, name }: Info): void {
changes.replaceNode(sourceFile, container, factory.createMappedTypeNode(/*readonlyToken*/ undefined,
factory.createTypeParameterDeclaration(name, factory.createTypeReferenceNode(constraint)), /*questionToken*/ undefined, typeNode));
factory.createTypeParameterDeclaration(name, factory.createTypeReferenceNode(constraint)), /*nameType*/ undefined, /*questionToken*/ undefined, typeNode));
}
}
@@ -44,6 +44,7 @@ namespace ts.codefix {
const mappedIntersectionType = factory.createMappedTypeNode(
hasEffectiveReadonlyModifier(indexSignature) ? factory.createModifier(SyntaxKind.ReadonlyKeyword) : undefined,
mappedTypeParameter,
/*nameType*/ undefined,
indexSignature.questionToken,
indexSignature.type);
const intersectionType = factory.createIntersectionTypeNode([