Allow +/- to prefix 'readonly' and '?' modifiers in mapped types

This commit is contained in:
Anders Hejlsberg 2018-02-12 16:20:49 -08:00
parent 74f01abfcf
commit a629acd8fd
6 changed files with 86 additions and 46 deletions

View File

@ -572,8 +572,10 @@ namespace ts {
}
const enum MappedTypeModifiers {
Readonly = 1 << 0,
Optional = 1 << 1,
IncludeReadonly = 1 << 0,
ExcludeReadonly = 1 << 1,
IncludeOptional = 1 << 2,
ExcludeOptional = 1 << 3,
}
const enum ExpandingFlags {
@ -2967,11 +2969,10 @@ namespace ts {
function createMappedTypeNodeFromType(type: MappedType) {
Debug.assert(!!(type.flags & TypeFlags.Object));
const readonlyToken = type.declaration && type.declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined;
const questionToken = type.declaration && type.declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined;
const readonlyToken = type.declaration.readonlyToken ? <ReadonlyToken | PlusToken | MinusToken>createToken(type.declaration.readonlyToken.kind) : undefined;
const questionToken = type.declaration.questionToken ? <QuestionToken | PlusToken | MinusToken>createToken(type.declaration.questionToken.kind) : undefined;
const typeParameterNode = typeParameterToDeclaration(getTypeParameterFromMappedType(type), context, getConstraintTypeFromMappedType(type));
const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context);
const mappedTypeNode = createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode);
return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
}
@ -5819,8 +5820,9 @@ namespace ts {
function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
const indexInfo = getIndexInfoOfType(type.source, IndexKind.String);
const readonlyMask = type.mappedType.declaration.readonlyToken ? false : true;
const optionalMask = type.mappedType.declaration.questionToken ? 0 : SymbolFlags.Optional;
const modifiers = getMappedTypeModifiers(type.mappedType);
const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true;
const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional;
const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType), readonlyMask && indexInfo.isReadonly);
const members = createSymbolTable();
for (const prop of getPropertiesOfType(type.source)) {
@ -5846,8 +5848,7 @@ namespace ts {
const constraintType = getConstraintTypeFromMappedType(type);
const templateType = getTemplateTypeFromMappedType(<MappedType>type.target || type);
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
const templateReadonly = !!type.declaration.readonlyToken;
const templateOptional = !!type.declaration.questionToken;
const templateModifiers = getMappedTypeModifiers(type);
const constraintDeclaration = type.declaration.typeParameter.constraint;
if (constraintDeclaration.kind === SyntaxKind.TypeOperator &&
(<TypeOperatorNode>constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) {
@ -5888,10 +5889,17 @@ namespace ts {
if (t.flags & TypeFlags.StringLiteral) {
const propName = escapeLeadingUnderscores((<StringLiteralType>t).value);
const modifiersProp = getPropertyOfType(modifiersType, propName);
const isOptional = templateOptional || !!(modifiersProp && modifiersProp.flags & SymbolFlags.Optional);
const checkFlags = templateReadonly || modifiersProp && isReadonlySymbol(modifiersProp) ? CheckFlags.Readonly : 0;
const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, checkFlags);
prop.type = propType;
const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional ||
!(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional);
const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly ||
!(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp));
const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, isReadonly ? CheckFlags.Readonly : 0);
// 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.
prop.type = strictNullChecks && isOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) :
strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
propType;
if (propertySymbol) {
prop.syntheticOrigin = propertySymbol;
prop.declarations = propertySymbol.declarations;
@ -5900,7 +5908,7 @@ namespace ts {
members.set(propName, prop);
}
else if (t.flags & (TypeFlags.Any | TypeFlags.String)) {
stringIndexInfo = createIndexInfo(propType, templateReadonly);
stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
}
}
}
@ -5918,7 +5926,7 @@ namespace ts {
function getTemplateTypeFromMappedType(type: MappedType) {
return type.templateType ||
(type.templateType = type.declaration.type ?
instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!type.declaration.questionToken), type.mapper || identityMapper) :
instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper || identityMapper) :
unknownType);
}
@ -5946,18 +5954,24 @@ namespace ts {
}
function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers {
return (type.declaration.readonlyToken ? MappedTypeModifiers.Readonly : 0) |
(type.declaration.questionToken ? MappedTypeModifiers.Optional : 0);
const declaration = type.declaration;
return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) |
(declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0);
}
function getCombinedMappedTypeModifiers(type: MappedType): MappedTypeModifiers {
function getMappedTypeOptionality(type: MappedType): number {
const modifiers = getMappedTypeModifiers(type);
return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0;
}
function getCombinedMappedTypeOptionality(type: MappedType): number {
const optionality = getMappedTypeOptionality(type);
const modifiersType = getModifiersTypeFromMappedType(type);
return getMappedTypeModifiers(type) |
(isGenericMappedType(modifiersType) ? getMappedTypeModifiers(<MappedType>modifiersType) : 0);
return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(<MappedType>modifiersType) : 0);
}
function isPartialMappedType(type: Type) {
return getObjectFlags(type) & ObjectFlags.Mapped && !!(<MappedType>type).declaration.questionToken;
return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(<MappedType>type) & MappedTypeModifiers.IncludeOptional);
}
function isGenericMappedType(type: Type): type is MappedType {
@ -9960,7 +9974,7 @@ namespace ts {
if (target.flags & TypeFlags.TypeParameter) {
// A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
if (!(<MappedType>source).declaration.questionToken) {
if (!(getMappedTypeModifiers(<MappedType>source) & MappedTypeModifiers.IncludeOptional)) {
const templateType = getTemplateTypeFromMappedType(<MappedType>source);
const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(<MappedType>source));
if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) {
@ -9999,6 +10013,8 @@ namespace ts {
else if (isGenericMappedType(target)) {
// A source type T is related to a target type { [P in X]: T[P] }
const template = getTemplateTypeFromMappedType(<MappedType>target);
const modifiers = getMappedTypeModifiers(<MappedType>target);
if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) {
if (template.flags & TypeFlags.IndexedAccess && (<IndexedAccessType>template).objectType === source &&
(<IndexedAccessType>template).indexType === getTypeParameterFromMappedType(<MappedType>target)) {
return Ternary.True;
@ -10013,6 +10029,7 @@ namespace ts {
}
}
}
}
if (source.flags & TypeFlags.TypeParameter) {
let constraint = getConstraintForRelation(<TypeParameter>source);
@ -10162,8 +10179,7 @@ namespace ts {
function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary {
const modifiersRelated = relation === comparableRelation || (
relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) :
!(getCombinedMappedTypeModifiers(source) & MappedTypeModifiers.Optional) ||
getCombinedMappedTypeModifiers(target) & MappedTypeModifiers.Optional);
getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target));
if (modifiersRelated) {
let result: Ternary;
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
@ -20345,7 +20361,7 @@ namespace ts {
const indexType = (<IndexedAccessType>type).indexType;
if (isTypeAssignableTo(indexType, getIndexType(objectType))) {
if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) &&
getObjectFlags(objectType) & ObjectFlags.Mapped && (<MappedType>objectType).declaration.readonlyToken) {
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(<MappedType>objectType) & MappedTypeModifiers.IncludeReadonly) {
error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
}
return type;

View File

@ -593,7 +593,9 @@ namespace ts {
writeLine();
increaseIndent();
if (node.readonlyToken) {
write("readonly ");
write(node.readonlyToken.kind === SyntaxKind.PlusToken ? "+readonly " :
node.readonlyToken.kind === SyntaxKind.MinusToken ? "-readonly " :
"readonly ");
}
write("[");
writeEntityName(node.typeParameter.name);
@ -601,7 +603,9 @@ namespace ts {
emitType(node.typeParameter.constraint);
write("]");
if (node.questionToken) {
write("?");
write(node.questionToken.kind === SyntaxKind.PlusToken ? "+?" :
node.questionToken.kind === SyntaxKind.MinusToken ? "-?" :
"?");
}
write(": ");
emitType(node.type);

View File

@ -1251,14 +1251,20 @@ namespace ts {
}
if (node.readonlyToken) {
emit(node.readonlyToken);
if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) {
writeKeyword("readonly");
}
writeSpace();
}
writePunctuation("[");
pipelineEmitWithNotification(EmitHint.MappedTypeParameter, node.typeParameter);
writePunctuation("]");
emitIfPresent(node.questionToken);
if (node.questionToken) {
emit(node.questionToken);
if (node.questionToken.kind !== SyntaxKind.QuestionToken) {
writePunctuation("?");
}
}
writePunctuation(":");
writeSpace();
emit(node.type);

View File

@ -804,7 +804,7 @@ namespace ts {
: node;
}
export function createMappedTypeNode(readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode {
export function createMappedTypeNode(readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
const node = createSynthesizedNode(SyntaxKind.MappedType) as MappedTypeNode;
node.readonlyToken = readonlyToken;
node.typeParameter = typeParameter;
@ -813,7 +813,7 @@ namespace ts {
return node;
}
export function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode {
export function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
return node.readonlyToken !== readonlyToken
|| node.typeParameter !== typeParameter
|| node.questionToken !== questionToken

View File

@ -695,7 +695,7 @@ namespace ts {
else if (token() === SyntaxKind.OpenBraceToken ||
lookAhead(() => token() === SyntaxKind.StringLiteral)) {
result.jsonObject = parseObjectLiteralExpression();
sourceFile.endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, /*reportAtCurrentPosition*/ false, Diagnostics.Unexpected_token);
sourceFile.endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token);
}
else {
parseExpected(SyntaxKind.OpenBraceToken);
@ -1135,10 +1135,10 @@ namespace ts {
return undefined;
}
function parseExpectedToken<TKind extends SyntaxKind>(t: TKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Token<TKind>;
function parseExpectedToken(t: SyntaxKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Node {
function parseExpectedToken<TKind extends SyntaxKind>(t: TKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Token<TKind>;
function parseExpectedToken(t: SyntaxKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Node {
return parseOptionalToken(t) ||
createMissingNode(t, reportAtCurrentPosition, diagnosticMessage, arg0);
createMissingNode(t, /*reportAtCurrentPosition*/ false, diagnosticMessage || Diagnostics._0_expected, arg0 || tokenToString(t));
}
function parseTokenNode<T extends Node>(): T {
@ -2113,7 +2113,7 @@ namespace ts {
literal = parseTemplateMiddleOrTemplateTail();
}
else {
literal = <TemplateTail>parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken));
literal = <TemplateTail>parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken));
}
span.literal = literal;
@ -2607,6 +2607,9 @@ namespace ts {
function isStartOfMappedType() {
nextToken();
if (token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) {
return nextToken() === SyntaxKind.ReadonlyKeyword;
}
if (token() === SyntaxKind.ReadonlyKeyword) {
nextToken();
}
@ -2624,11 +2627,21 @@ namespace ts {
function parseMappedType() {
const node = <MappedTypeNode>createNode(SyntaxKind.MappedType);
parseExpected(SyntaxKind.OpenBraceToken);
node.readonlyToken = parseOptionalToken(SyntaxKind.ReadonlyKeyword);
if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) {
node.readonlyToken = parseTokenNode();
if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) {
parseExpectedToken(SyntaxKind.ReadonlyKeyword);
}
}
parseExpected(SyntaxKind.OpenBracketToken);
node.typeParameter = parseMappedTypeParameter();
parseExpected(SyntaxKind.CloseBracketToken);
node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) {
node.questionToken = parseTokenNode();
if (node.questionToken.kind !== SyntaxKind.QuestionToken) {
parseExpectedToken(SyntaxKind.QuestionToken);
}
}
node.type = parseTypeAnnotation();
parseSemicolon();
parseExpected(SyntaxKind.CloseBraceToken);
@ -3242,7 +3255,7 @@ namespace ts {
node.parameters = createNodeArray<ParameterDeclaration>([parameter], parameter.pos, parameter.end);
node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, "=>");
node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken);
node.body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier);
return addJSDocComment(finishNode(node));
@ -3273,7 +3286,7 @@ namespace ts {
// If we have an arrow, then try to parse the body. Even if not, try to parse if we
// have an opening brace, just in case we're in an error state.
const lastToken = token();
arrowFunction.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, "=>");
arrowFunction.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken);
arrowFunction.body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken)
? parseArrowFunctionExpressionBody(isAsync)
: parseIdentifier();
@ -3539,8 +3552,7 @@ namespace ts {
node.condition = leftOperand;
node.questionToken = questionToken;
node.whenTrue = doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher);
node.colonToken = parseExpectedToken(SyntaxKind.ColonToken, /*reportAtCurrentPosition*/ false,
Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken));
node.colonToken = parseExpectedToken(SyntaxKind.ColonToken);
node.whenFalse = nodeIsPresent(node.colonToken)
? parseAssignmentExpressionOrHigher()
: createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken));
@ -4014,7 +4026,7 @@ namespace ts {
// If it wasn't then just try to parse out a '.' and report an error.
const node = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
node.expression = expression;
parseExpectedToken(SyntaxKind.DotToken, /*reportAtCurrentPosition*/ false, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access);
parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access);
node.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
return finishNode(node);
}

View File

@ -661,6 +661,8 @@ namespace ts {
export type AtToken = Token<SyntaxKind.AtToken>;
export type ReadonlyToken = Token<SyntaxKind.ReadonlyKeyword>;
export type AwaitKeywordToken = Token<SyntaxKind.AwaitKeyword>;
export type PlusToken = Token<SyntaxKind.PlusToken>;
export type MinusToken = Token<SyntaxKind.MinusToken>;
export type Modifier
= Token<SyntaxKind.AbstractKeyword>
@ -1158,9 +1160,9 @@ namespace ts {
export interface MappedTypeNode extends TypeNode, Declaration {
kind: SyntaxKind.MappedType;
readonlyToken?: ReadonlyToken;
readonlyToken?: ReadonlyToken | PlusToken | MinusToken;
typeParameter: TypeParameterDeclaration;
questionToken?: QuestionToken;
questionToken?: QuestionToken | PlusToken | MinusToken;
type?: TypeNode;
}