From 93896799936f1c42ec61b4223edc4c4ad7cbc7e7 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 26 Dec 2018 14:38:29 +0000 Subject: [PATCH 01/21] Initial commit --- src/compiler/checker.ts | 54 ++++++++++++++++++++++++++-- src/compiler/diagnosticMessages.json | 4 +++ src/compiler/factory.ts | 3 +- src/compiler/parser.ts | 47 ++++++++++++------------ src/compiler/types.ts | 4 +++ 5 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 356ddc2a28e1a..4c7dba8842a51 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3936,7 +3936,7 @@ namespace ts { const defaultParameter = getDefaultFromTypeParameter(type); const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); context.flags = savedContextFlags; - return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); + return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode, type.uniformityConstraint); } function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { @@ -8223,6 +8223,11 @@ namespace ts { return decl && getEffectiveConstraintOfTypeParameter(decl); } + function getUniformityConstraintDeclaration(type: TypeParameter) { + const decl = type.symbol && getDeclarationOfKind(type.symbol, SyntaxKind.TypeParameter); + return decl && decl.uniformityConstraint; + } + function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { let inferences: Type[] | undefined; if (typeParameter.symbol) { @@ -8283,6 +8288,13 @@ namespace ts { return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; } + function getUniformityConstraintFromTypeParameter(typeParameter: TypeParameter): boolean | undefined { + if(typeParameter.uniformityConstraint === undefined) { + typeParameter.uniformityConstraint = getUniformityConstraintDeclaration(typeParameter); + } + return typeParameter.uniformityConstraint; + } + function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent; @@ -14481,6 +14493,14 @@ namespace ts { inference.inferredType = inferredType; const constraint = getConstraintOfTypeParameter(inference.typeParameter); + const uniformityConstraint = getUniformityConstraintFromTypeParameter(inference.typeParameter); + if (uniformityConstraint) { + if (!isUniformType(inferredType)) { + const decl = getDeclarationOfKind(inference.typeParameter.symbol, SyntaxKind.TypeParameter); + error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType)); + } + // jw todo + } if (constraint) { const instantiatedConstraint = instantiateType(constraint, context); if (!context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { @@ -15769,7 +15789,8 @@ namespace ts { function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands const target = getReferenceCandidate(typeOfExpr.expression); - if (!isMatchingReference(reference, target)) { + const isNarrowableUnderUniformity = (type.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(type) && getTypeOfNode(target) === type; + if (!isMatchingReference(reference, target) && !isNarrowableUnderUniformity) { // For a reference of the form 'x.y', where 'x' has a narrowable declared type, a // 'typeof x === ...' type guard resets the narrowed type of 'y' to its declared type. if (containsMatchingReference(reference, target) && hasNarrowableDeclaredType(target)) { @@ -19605,6 +19626,14 @@ namespace ts { createTupleType(append(types.slice(0, spreadIndex), getUnionType(types.slice(spreadIndex))), spreadIndex, /*hasRestElement*/ true); } + function isUniformType(type: Type): boolean { + // jw todo: more cases + if (isUnitType(type) || (type.flags & TypeFlags.Boolean)) { + return true; + } + return false; + } + function checkTypeArguments(signature: Signature, typeArgumentNodes: ReadonlyArray, reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { const isJavascript = isInJSFile(signature.declaration); const typeParameters = signature.typeParameters!; @@ -19613,6 +19642,15 @@ namespace ts { for (let i = 0; i < typeArgumentNodes.length; i++) { Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); const constraint = getConstraintOfTypeParameter(typeParameters[i]); + const uniformityConstraint = getUniformityConstraintFromTypeParameter(typeParameters[i]); + if (uniformityConstraint) { + if (!isUniformType(typeArgumentTypes[i])) { + const decl = getDeclarationOfKind(typeParameters[i].symbol, SyntaxKind.TypeParameter); + error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArgumentTypes[i])); + //error + } + // jw todo + } if (constraint) { const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; @@ -23566,6 +23604,18 @@ namespace ts { let result = true; for (let i = 0; i < typeParameters.length; i++) { const constraint = getConstraintOfTypeParameter(typeParameters[i]); + const uniformityConstraint = getUniformityConstraintFromTypeParameter(typeParameters[i]); + if (uniformityConstraint) { + if (!typeArguments) { + typeArguments = getEffectiveTypeArguments(node, typeParameters); + mapper = createTypeMapper(typeParameters, typeArguments); + } + if (!isUniformType(typeArguments[i])) { + error(node, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArguments[i])); + // error + } + // jw todo + } if (constraint) { if (!typeArguments) { typeArguments = getEffectiveTypeArguments(node, typeParameters); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2cbf5784d0786..e00d437cf948b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2537,6 +2537,10 @@ "category": "Error", "code": 2743 }, + "Type {0} does not satisfy uniformity constraint. Values of type {0} do not behave identically under typeof": { + "category": "Error", + "code": 2744 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ec19d2c906c12..d2d8ff22b3411 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -300,11 +300,12 @@ namespace ts { // Signature elements - export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) { + export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: boolean) { const node = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; node.name = asName(name); node.constraint = constraint; node.default = defaultType; + node.uniformityConstraint = uniformityConstraint; return node; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f976f77ea985a..20e832530f185 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3,7 +3,7 @@ namespace ts { None = 0, Yield = 1 << 0, Await = 1 << 1, - Type = 1 << 2, + Type = 1 << 2, IgnoreMissingOpenBrace = 1 << 4, JSDoc = 1 << 5, } @@ -465,9 +465,9 @@ namespace ts { return visitNode(cbNode, (node as JSDocTag).tagName) || ((node as JSDocPropertyLikeTag).isNameFirst ? visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).typeExpression) + visitNode(cbNode, (node).typeExpression) : visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).name)); + visitNode(cbNode, (node).name)); case SyntaxKind.JSDocAugmentsTag: return visitNode(cbNode, (node as JSDocTag).tagName) || visitNode(cbNode, (node).class); @@ -480,9 +480,9 @@ namespace ts { ((node as JSDocTypedefTag).typeExpression && (node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression ? visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).fullName) + visitNode(cbNode, (node).fullName) : visitNode(cbNode, (node).fullName) || - visitNode(cbNode, (node).typeExpression)); + visitNode(cbNode, (node).typeExpression)); case SyntaxKind.JSDocCallbackTag: return visitNode(cbNode, (node as JSDocTag).tagName) || visitNode(cbNode, (node as JSDocCallbackTag).fullName) || @@ -752,7 +752,7 @@ namespace ts { statement.expression = parseLiteralNode() as StringLiteral | NumericLiteral; break; } - // falls through + // falls through default: statement.expression = parseObjectLiteralExpression(); break; @@ -1251,7 +1251,7 @@ namespace ts { const p = pos! >= 0 ? pos! : scanner.getStartPos(); return isNodeKind(kind) || kind === SyntaxKind.Unknown ? new NodeConstructor(kind, p, p) : kind === SyntaxKind.Identifier ? new IdentifierConstructor(kind, p, p) : - new TokenConstructor(kind, p, p); + new TokenConstructor(kind, p, p); } function createNodeWithJSDoc(kind: SyntaxKind, pos?: number): Node { @@ -1517,7 +1517,7 @@ namespace ts { case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) return true; } - // falls through + // falls through case ParsingContext.ArgumentExpressions: return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: @@ -2411,6 +2411,7 @@ namespace ts { function parseTypeParameter(): TypeParameterDeclaration { const node = createNode(SyntaxKind.TypeParameter); node.name = parseIdentifier(); + node.uniformityConstraint = parseOptional(SyntaxKind.ExclamationToken); if (parseOptional(SyntaxKind.ExtendsKeyword)) { // It's not uncommon for people to write improper constraints to a generic. If the // user writes a constraint that is an expression and not an actual type, then parse @@ -3980,7 +3981,7 @@ namespace ts { if (isAwaitExpression()) { return parseAwaitExpression(); } - // falls through + // falls through default: return parseUpdateExpression(); } @@ -4014,8 +4015,8 @@ namespace ts { if (sourceFile.languageVariant !== LanguageVariant.JSX) { return false; } - // We are in JSX context and the token is part of JSXElement. - // falls through + // We are in JSX context and the token is part of JSXElement. + // falls through default: return true; } @@ -5906,8 +5907,8 @@ namespace ts { function tryParseTypeArguments(): NodeArray | undefined { return token() === SyntaxKind.LessThanToken - ? parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) - : undefined; + ? parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) + : undefined; } function isHeritageClause(): boolean { @@ -6244,8 +6245,8 @@ namespace ts { // Try to use the first top-level import/export when available, then // fall back to looking for an 'import.meta' somewhere in the tree if necessary. sourceFile.externalModuleIndicator = - forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || - getImportMetaIfNecessary(sourceFile); + forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || + getImportMetaIfNecessary(sourceFile); } function isAnExternalModuleIndicatorNode(node: Node) { @@ -6254,8 +6255,8 @@ namespace ts { || node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ExportAssignment || node.kind === SyntaxKind.ExportDeclaration - ? node - : undefined; + ? node + : undefined; } function getImportMetaIfNecessary(sourceFile: SourceFile) { @@ -6623,7 +6624,7 @@ namespace ts { break; case SyntaxKind.AtToken: scanner.setTextPos(scanner.getTextPos() - 1); - // falls through + // falls through case SyntaxKind.EndOfFileToken: // Done break loop; @@ -6657,8 +6658,8 @@ namespace ts { indent += 1; break; } - // record the * as a comment - // falls through + // record the * as a comment + // falls through default: state = JSDocState.SavingComments; // leading identifiers start recording as well pushComment(scanner.getTokenText()); @@ -7854,7 +7855,7 @@ namespace ts { return; } if (pragma.args) { - const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {}; + const argument: { [index: string]: string | { value: string, pos: number, end: number } } = {}; for (const arg of pragma.args) { const matcher = getNamedArgRegEx(arg.name); const matchResult = matcher.exec(text); @@ -7911,11 +7912,11 @@ namespace ts { return; } - function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" { + function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): { [index: string]: string } | "fail" { if (!text) return {}; if (!pragma.args) return {}; const args = text.split(/\s+/); - const argMap: {[index: string]: string} = {}; + const argMap: { [index: string]: string } = {}; for (let i = 0; i < pragma.args.length; i++) { const argument = pragma.args[i]; if (!args[i] && !argument.optional) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 495465f70326c..7eee555a67691 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -845,6 +845,8 @@ namespace ts { // For error recovery purposes. expression?: Expression; + + uniformityConstraint?: boolean; } export interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { @@ -4175,6 +4177,8 @@ namespace ts { isThisType?: boolean; /* @internal */ resolvedDefaultType?: Type; + /* @internal */ + uniformityConstraint?: boolean; } // Indexed access types (TypeFlags.IndexedAccess) From e8026c68dbacf9bfc70796f6c42dcff53b68de82 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 26 Dec 2018 16:30:56 +0000 Subject: [PATCH 02/21] Add more uniform cases --- src/compiler/checker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4c7dba8842a51..ecb9bf4a37413 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19628,9 +19628,12 @@ namespace ts { function isUniformType(type: Type): boolean { // jw todo: more cases - if (isUnitType(type) || (type.flags & TypeFlags.Boolean)) { + if (isUnitType(type) || (type.flags & TypeFlags.Primitive)) { return true; } + if (type.flags & TypeFlags.TypeParameter) { + return !!getUniformityConstraintFromTypeParameter( type); + } return false; } From 382e2351791b90b6528fafd5b09702011be8600f Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 26 Dec 2018 16:35:54 +0000 Subject: [PATCH 03/21] Undo indenting --- src/compiler/parser.ts | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 20e832530f185..6dc785bb2bc16 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -465,9 +465,9 @@ namespace ts { return visitNode(cbNode, (node as JSDocTag).tagName) || ((node as JSDocPropertyLikeTag).isNameFirst ? visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).typeExpression) + visitNode(cbNode, (node).typeExpression) : visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).name)); + visitNode(cbNode, (node).name)); case SyntaxKind.JSDocAugmentsTag: return visitNode(cbNode, (node as JSDocTag).tagName) || visitNode(cbNode, (node).class); @@ -480,9 +480,9 @@ namespace ts { ((node as JSDocTypedefTag).typeExpression && (node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression ? visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).fullName) + visitNode(cbNode, (node).fullName) : visitNode(cbNode, (node).fullName) || - visitNode(cbNode, (node).typeExpression)); + visitNode(cbNode, (node).typeExpression)); case SyntaxKind.JSDocCallbackTag: return visitNode(cbNode, (node as JSDocTag).tagName) || visitNode(cbNode, (node as JSDocCallbackTag).fullName) || @@ -752,7 +752,7 @@ namespace ts { statement.expression = parseLiteralNode() as StringLiteral | NumericLiteral; break; } - // falls through + // falls through default: statement.expression = parseObjectLiteralExpression(); break; @@ -1251,7 +1251,7 @@ namespace ts { const p = pos! >= 0 ? pos! : scanner.getStartPos(); return isNodeKind(kind) || kind === SyntaxKind.Unknown ? new NodeConstructor(kind, p, p) : kind === SyntaxKind.Identifier ? new IdentifierConstructor(kind, p, p) : - new TokenConstructor(kind, p, p); + new TokenConstructor(kind, p, p); } function createNodeWithJSDoc(kind: SyntaxKind, pos?: number): Node { @@ -1517,7 +1517,7 @@ namespace ts { case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) return true; } - // falls through + // falls through case ParsingContext.ArgumentExpressions: return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: @@ -3981,7 +3981,7 @@ namespace ts { if (isAwaitExpression()) { return parseAwaitExpression(); } - // falls through + // falls through default: return parseUpdateExpression(); } @@ -4015,8 +4015,8 @@ namespace ts { if (sourceFile.languageVariant !== LanguageVariant.JSX) { return false; } - // We are in JSX context and the token is part of JSXElement. - // falls through + // We are in JSX context and the token is part of JSXElement. + // falls through default: return true; } @@ -5907,8 +5907,8 @@ namespace ts { function tryParseTypeArguments(): NodeArray | undefined { return token() === SyntaxKind.LessThanToken - ? parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) - : undefined; + ? parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) + : undefined; } function isHeritageClause(): boolean { @@ -6245,8 +6245,8 @@ namespace ts { // Try to use the first top-level import/export when available, then // fall back to looking for an 'import.meta' somewhere in the tree if necessary. sourceFile.externalModuleIndicator = - forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || - getImportMetaIfNecessary(sourceFile); + forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || + getImportMetaIfNecessary(sourceFile); } function isAnExternalModuleIndicatorNode(node: Node) { @@ -6255,8 +6255,8 @@ namespace ts { || node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ExportAssignment || node.kind === SyntaxKind.ExportDeclaration - ? node - : undefined; + ? node + : undefined; } function getImportMetaIfNecessary(sourceFile: SourceFile) { @@ -6624,7 +6624,7 @@ namespace ts { break; case SyntaxKind.AtToken: scanner.setTextPos(scanner.getTextPos() - 1); - // falls through + // falls through case SyntaxKind.EndOfFileToken: // Done break loop; @@ -6658,8 +6658,8 @@ namespace ts { indent += 1; break; } - // record the * as a comment - // falls through + // record the * as a comment + // falls through default: state = JSDocState.SavingComments; // leading identifiers start recording as well pushComment(scanner.getTokenText()); @@ -7855,7 +7855,7 @@ namespace ts { return; } if (pragma.args) { - const argument: { [index: string]: string | { value: string, pos: number, end: number } } = {}; + const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {}; for (const arg of pragma.args) { const matcher = getNamedArgRegEx(arg.name); const matchResult = matcher.exec(text); @@ -7912,11 +7912,11 @@ namespace ts { return; } - function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): { [index: string]: string } | "fail" { + function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" { if (!text) return {}; if (!pragma.args) return {}; const args = text.split(/\s+/); - const argMap: { [index: string]: string } = {}; + const argMap: {[index: string]: string} = {}; for (let i = 0; i < pragma.args.length; i++) { const argument = pragma.args[i]; if (!args[i] && !argument.optional) { From 7422eff42747643d860e34b23be123c52bbf44bc Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 26 Dec 2018 17:03:02 +0000 Subject: [PATCH 04/21] Improve error --- src/compiler/checker.ts | 6 +++--- src/compiler/diagnosticMessages.json | 2 +- src/compiler/emitter.ts | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ecb9bf4a37413..4a7b7b9ece308 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14497,7 +14497,7 @@ namespace ts { if (uniformityConstraint) { if (!isUniformType(inferredType)) { const decl = getDeclarationOfKind(inference.typeParameter.symbol, SyntaxKind.TypeParameter); - error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType)); + error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType), typeToString(inference.typeParameter)); } // jw todo } @@ -19649,7 +19649,7 @@ namespace ts { if (uniformityConstraint) { if (!isUniformType(typeArgumentTypes[i])) { const decl = getDeclarationOfKind(typeParameters[i].symbol, SyntaxKind.TypeParameter); - error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArgumentTypes[i])); + error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArgumentTypes[i]), typeToString(typeParameters[i])); //error } // jw todo @@ -23614,7 +23614,7 @@ namespace ts { mapper = createTypeMapper(typeParameters, typeArguments); } if (!isUniformType(typeArguments[i])) { - error(node, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArguments[i])); + error(node, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArguments[i]), typeToString(typeParameters[i])); // error } // jw todo diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e00d437cf948b..296b0a53ea6ce 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2537,7 +2537,7 @@ "category": "Error", "code": 2743 }, - "Type {0} does not satisfy uniformity constraint. Values of type {0} do not behave identically under typeof": { + "Type '{0}' does not satisfy uniformity constraint of type '{1}'. Values of type '{0}' do not behave identically under typeof": { "category": "Error", "code": 2744 }, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 9c09ed79a7780..dfe4b31a8d5a8 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1247,6 +1247,9 @@ namespace ts { function emitTypeParameter(node: TypeParameterDeclaration) { emit(node.name); + if (node.uniformityConstraint) { + writePunctuation("!"); + } if (node.constraint) { writeSpace(); writeKeyword("extends"); From 57d184a9d52d5f17352dd4b195925b58929dc84a Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sat, 29 Dec 2018 00:49:56 +0000 Subject: [PATCH 05/21] Narrow conditional types using uniform types --- src/compiler/checker.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4a7b7b9ece308..009af7a62eea0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15789,14 +15789,34 @@ namespace ts { function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands const target = getReferenceCandidate(typeOfExpr.expression); - const isNarrowableUnderUniformity = (type.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(type) && getTypeOfNode(target) === type; - if (!isMatchingReference(reference, target) && !isNarrowableUnderUniformity) { + if (!isMatchingReference(reference, target)) { // For a reference of the form 'x.y', where 'x' has a narrowable declared type, a // 'typeof x === ...' type guard resets the narrowed type of 'y' to its declared type. if (containsMatchingReference(reference, target) && hasNarrowableDeclaredType(target)) { return declaredType; } - return type; + if(!isIdentifier(target)) { + return type; + } + const targetType = getTypeOfExpression(target); + const isNarrowableUnderUniformity = (targetType.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(targetType); + if (!isNarrowableUnderUniformity) { + return type; + } + if (type.flags & TypeFlags.Conditional) { + if (!assumeTrue || operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + return type; + } + const substType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text); + if (!substType) { + return type; + } + const subst = getIntersectionType([targetType, substType]); + return getConditionalTypeInstantiation(type, createTypeMapper([targetType], [subst])); + } + if (targetType !== type) { + return type; + } } if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; From 485f5d0df392d1635c357141d04aea3ba7658dd6 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Thu, 3 Jan 2019 13:55:14 +0000 Subject: [PATCH 06/21] WIP of equality case --- src/compiler/checker.ts | 48 +++++++++++++++++++++++++++++++++++++---- src/compiler/factory.ts | 2 +- src/compiler/parser.ts | 4 +++- src/compiler/types.ts | 9 ++++++-- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 009af7a62eea0..864a287a9a6ce 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8225,7 +8225,7 @@ namespace ts { function getUniformityConstraintDeclaration(type: TypeParameter) { const decl = type.symbol && getDeclarationOfKind(type.symbol, SyntaxKind.TypeParameter); - return decl && decl.uniformityConstraint; + return decl && decl.uniformityConstraint ? decl.uniformityConstraint : 0; } function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { @@ -8288,7 +8288,7 @@ namespace ts { return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; } - function getUniformityConstraintFromTypeParameter(typeParameter: TypeParameter): boolean | undefined { + function getUniformityConstraintFromTypeParameter(typeParameter: TypeParameter): UniformityFlags { if(typeParameter.uniformityConstraint === undefined) { typeParameter.uniformityConstraint = getUniformityConstraintDeclaration(typeParameter); } @@ -15737,7 +15737,7 @@ namespace ts { if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { return declaredType; } - break; + return narrowTypeByUniformEquality(type, operator, left, right, assumeTrue); case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: @@ -15752,6 +15752,39 @@ namespace ts { return type; } + function narrowTypeByUniformEquality(type: Type, operator: SyntaxKind, left: Expression, right: Expression, assumeTrue: boolean): Type { + if (!assumeTrue || operator !== SyntaxKind.EqualsEqualsEqualsToken) { + return type; + } + const lType = getTypeOfExpression(left); + const rType = getTypeOfExpression(right); + let target, targetType; + let valueType; + if ((lType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(lType) & UniformityFlags.Equality)) { + target = left; + targetType = lType; + valueType = rType; + } + else if ((rType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(rType) & UniformityFlags.Equality)) { + target = right; + targetType = rType; + valueType = lType; + } else { + return type; + } + if (!isIdentifier(target) || !isUnitType(valueType)) { + return type; + } + if (type.flags & TypeFlags.Conditional) { + const subst = getIntersectionType([targetType, valueType]); + return getConditionalTypeInstantiation(type, createTypeMapper([targetType], [subst])); + } + if (targetType === type) { + return getIntersectionType([type, valueType]); + } + return type; + } + function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { if (type.flags & TypeFlags.Any) { return type; @@ -15799,7 +15832,7 @@ namespace ts { return type; } const targetType = getTypeOfExpression(target); - const isNarrowableUnderUniformity = (targetType.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(targetType); + const isNarrowableUnderUniformity = (targetType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(targetType) & UniformityFlags.TypeOf); if (!isNarrowableUnderUniformity) { return type; } @@ -22697,6 +22730,13 @@ namespace ts { checkTruthinessExpression(node.condition); const type1 = checkExpression(node.whenTrue, checkMode); const type2 = checkExpression(node.whenFalse, checkMode); + if (isBinaryExpression(node.condition) && node.condition.left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(node.condition.right)) { + const typeofTest = getTypeOfExpression((node.condition.left).expression); + if((typeofTest.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(typeofTest)) { +// const typeNode = createConditionalTypeNode(typeofTest, extendsTypeNode, type1, type2); +// return getTypeFromTypeNode(typeNode); + } + } return getUnionType([type1, type2], UnionReduction.Subtype); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d2d8ff22b3411..1740c797ebe78 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -300,7 +300,7 @@ namespace ts { // Signature elements - export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: boolean) { + export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags) { const node = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; node.name = asName(name); node.constraint = constraint; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6dc785bb2bc16..477f71a8c725b 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2411,7 +2411,9 @@ namespace ts { function parseTypeParameter(): TypeParameterDeclaration { const node = createNode(SyntaxKind.TypeParameter); node.name = parseIdentifier(); - node.uniformityConstraint = parseOptional(SyntaxKind.ExclamationToken); + node.uniformityConstraint = 0; + node.uniformityConstraint |= parseOptional(SyntaxKind.ExclamationToken) ? UniformityFlags.TypeOf : 0; + node.uniformityConstraint |= parseOptional(SyntaxKind.ExclamationToken) ? UniformityFlags.Equality : 0; if (parseOptional(SyntaxKind.ExtendsKeyword)) { // It's not uncommon for people to write improper constraints to a generic. If the // user writes a constraint that is an expression and not an actual type, then parse diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7eee555a67691..2f8d0cf04d54d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -846,7 +846,7 @@ namespace ts { // For error recovery purposes. expression?: Expression; - uniformityConstraint?: boolean; + uniformityConstraint?: UniformityFlags; } export interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { @@ -3961,6 +3961,11 @@ namespace ts { export interface EnumType extends Type { } + export const enum UniformityFlags { + TypeOf = 1 << 0, + Equality = 1 << 1 + } + export const enum ObjectFlags { Class = 1 << 0, // Class Interface = 1 << 1, // Interface @@ -4178,7 +4183,7 @@ namespace ts { /* @internal */ resolvedDefaultType?: Type; /* @internal */ - uniformityConstraint?: boolean; + uniformityConstraint?: UniformityFlags; } // Indexed access types (TypeFlags.IndexedAccess) From 9ffceee5f3dc710aa468acf9ac3ce3f7626c8b5c Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 8 Mar 2019 18:14:51 +0000 Subject: [PATCH 07/21] Fix alias case --- src/compiler/checker.ts | 42 ++++++----------------------------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 01f6d358e5463..93276b2794d93 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16168,7 +16168,6 @@ namespace ts { if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { return declaredType; } - return narrowTypeByUniformEquality(type, operator, left, right, assumeTrue); case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: @@ -16183,39 +16182,6 @@ namespace ts { return type; } - function narrowTypeByUniformEquality(type: Type, operator: SyntaxKind, left: Expression, right: Expression, assumeTrue: boolean): Type { - if (!assumeTrue || operator !== SyntaxKind.EqualsEqualsEqualsToken) { - return type; - } - const lType = getTypeOfExpression(left); - const rType = getTypeOfExpression(right); - let target, targetType; - let valueType; - if ((lType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(lType) & UniformityFlags.Equality)) { - target = left; - targetType = lType; - valueType = rType; - } - else if ((rType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(rType) & UniformityFlags.Equality)) { - target = right; - targetType = rType; - valueType = lType; - } else { - return type; - } - if (!isIdentifier(target) || !isUnitType(valueType)) { - return type; - } - if (type.flags & TypeFlags.Conditional) { - const subst = getIntersectionType([targetType, valueType]); - return getConditionalTypeInstantiation(type, createTypeMapper([targetType], [subst])); - } - if (targetType === type) { - return getIntersectionType([type, valueType]); - } - return type; - } - function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { if (type.flags & TypeFlags.Any) { return type; @@ -16272,7 +16238,8 @@ namespace ts { return type; } const targetType = getTypeOfExpression(target); - const isNarrowableUnderUniformity = (targetType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(targetType) & UniformityFlags.TypeOf); + const isNarrowableUnderUniformity = + (targetType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(targetType) & UniformityFlags.TypeOf); if (!isNarrowableUnderUniformity) { return type; } @@ -16280,12 +16247,15 @@ namespace ts { if (!assumeTrue || operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { return type; } + const cond = type; const substType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text); if (!substType) { return type; } const subst = getIntersectionType([targetType, substType]); - return getConditionalTypeInstantiation(type, createTypeMapper([targetType], [subst])); + const mapper = createTypeMapper([targetType], [subst]); + const narrowedMapper = combineTypeMappers(cond.mapper, mapper); + return instantiateType(cond, narrowedMapper); } if (targetType !== type) { return type; From de2a9eaf543725215a9828161d433c041bd37fb3 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 8 Mar 2019 19:38:35 +0000 Subject: [PATCH 08/21] Remove equality constraint --- src/compiler/parser.ts | 1 - src/compiler/types.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cdb40b3e08bee..7e5b2b9ab8455 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2417,7 +2417,6 @@ namespace ts { node.name = parseIdentifier(); node.uniformityConstraint = 0; node.uniformityConstraint |= parseOptional(SyntaxKind.ExclamationToken) ? UniformityFlags.TypeOf : 0; - node.uniformityConstraint |= parseOptional(SyntaxKind.ExclamationToken) ? UniformityFlags.Equality : 0; if (parseOptional(SyntaxKind.ExtendsKeyword)) { // It's not uncommon for people to write improper constraints to a generic. If the // user writes a constraint that is an expression and not an actual type, then parse diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cfdbf095d8499..16224ed0a7449 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3984,7 +3984,6 @@ namespace ts { export const enum UniformityFlags { TypeOf = 1 << 0, - Equality = 1 << 1 } export const enum ObjectFlags { From 0c5fbc9ffa95d198eaccfaee99ce139bae402a00 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 8 Mar 2019 19:38:48 +0000 Subject: [PATCH 09/21] Infer conditional types --- src/compiler/checker.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 93276b2794d93..3a946ca5d89fb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -527,6 +527,7 @@ namespace ts { let deferredGlobalExcludeSymbol: Symbol; let deferredGlobalPickSymbol: Symbol; let deferredGlobalBigIntType: ObjectType; + let deferredGlobalCondSymbol: Symbol; const allPotentiallyUnusedIdentifiers = createMap(); // key is file name @@ -9086,6 +9087,10 @@ namespace ts { return deferredGlobalPickSymbol || (deferredGlobalPickSymbol = getGlobalSymbol("Pick" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 } + function getGlobalCondSymbol(): Symbol { + return deferredGlobalCondSymbol || (deferredGlobalCondSymbol = getGlobalSymbol("Cond" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 + } + function getGlobalBigIntType(reportErrors: boolean) { return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } @@ -16168,6 +16173,7 @@ namespace ts { if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { return declaredType; } + break; case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: @@ -16258,7 +16264,7 @@ namespace ts { return instantiateType(cond, narrowedMapper); } if (targetType !== type) { - return type; + return declaredType; } } if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { @@ -23251,10 +23257,16 @@ namespace ts { const type1 = checkExpression(node.whenTrue, checkMode); const type2 = checkExpression(node.whenFalse, checkMode); if (isBinaryExpression(node.condition) && node.condition.left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(node.condition.right)) { - const typeofTest = getTypeOfExpression((node.condition.left).expression); - if((typeofTest.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(typeofTest)) { -// const typeNode = createConditionalTypeNode(typeofTest, extendsTypeNode, type1, type2); -// return getTypeFromTypeNode(typeNode); + const extendsType = typeofTypesByName.get(node.condition.right.text); + if (extendsType !== undefined) { + const typeofTest = getTypeOfExpression((node.condition.left).expression); + if((typeofTest.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(typeofTest)) { + const condTypeAlias = getGlobalCondSymbol(); + if (!condTypeAlias) { + return errorType; + } + return getTypeAliasInstantiation(condTypeAlias, [typeofTest, extendsType, type1, type2]); + } } } return getUnionType([type1, type2], UnionReduction.Subtype); From 4b178dd7d7ee8d066995efb8b3d5c89005cdcb3f Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 8 Mar 2019 20:12:08 +0000 Subject: [PATCH 10/21] Better error locations --- src/compiler/checker.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3a946ca5d89fb..d707edf122c6c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14964,10 +14964,8 @@ namespace ts { const uniformityConstraint = getUniformityConstraintFromTypeParameter(inference.typeParameter); if (uniformityConstraint) { if (!isUniformType(inferredType)) { - const decl = getDeclarationOfKind(inference.typeParameter.symbol, SyntaxKind.TypeParameter); - error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType), typeToString(inference.typeParameter)); + error(undefined, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType), typeToString(inference.typeParameter)); } - // jw todo } if (constraint) { context.flags |= InferenceFlags.NoFixing; @@ -20237,11 +20235,8 @@ namespace ts { const uniformityConstraint = getUniformityConstraintFromTypeParameter(typeParameters[i]); if (uniformityConstraint) { if (!isUniformType(typeArgumentTypes[i])) { - const decl = getDeclarationOfKind(typeParameters[i].symbol, SyntaxKind.TypeParameter); - error(decl, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArgumentTypes[i]), typeToString(typeParameters[i])); - //error + error(typeArgumentNodes[i], Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArgumentTypes[i]), typeToString(typeParameters[i])); } - // jw todo } if (constraint) { const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; @@ -24226,10 +24221,8 @@ namespace ts { mapper = createTypeMapper(typeParameters, typeArguments); } if (!isUniformType(typeArguments[i])) { - error(node, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArguments[i]), typeToString(typeParameters[i])); - // error + error(node.typeArguments && node.typeArguments[i], Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArguments[i]), typeToString(typeParameters[i])); } - // jw todo } if (constraint) { if (!typeArguments) { From 3404b0c744a4e46aea9423b3e3aaec5a6865637d Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 8 Mar 2019 22:02:32 +0000 Subject: [PATCH 11/21] Cond -> If --- src/compiler/checker.ts | 12 ++++++------ src/lib/es5.d.ts | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d707edf122c6c..87f9069bba253 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -527,7 +527,7 @@ namespace ts { let deferredGlobalExcludeSymbol: Symbol; let deferredGlobalPickSymbol: Symbol; let deferredGlobalBigIntType: ObjectType; - let deferredGlobalCondSymbol: Symbol; + let deferredGlobalIfTypeSymbol: Symbol; const allPotentiallyUnusedIdentifiers = createMap(); // key is file name @@ -9087,8 +9087,8 @@ namespace ts { return deferredGlobalPickSymbol || (deferredGlobalPickSymbol = getGlobalSymbol("Pick" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 } - function getGlobalCondSymbol(): Symbol { - return deferredGlobalCondSymbol || (deferredGlobalCondSymbol = getGlobalSymbol("Cond" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 + function getGlobalIfTypeSymbol(): Symbol { + return deferredGlobalIfTypeSymbol || (deferredGlobalIfTypeSymbol = getGlobalSymbol("If" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 } function getGlobalBigIntType(reportErrors: boolean) { @@ -23256,11 +23256,11 @@ namespace ts { if (extendsType !== undefined) { const typeofTest = getTypeOfExpression((node.condition.left).expression); if((typeofTest.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(typeofTest)) { - const condTypeAlias = getGlobalCondSymbol(); - if (!condTypeAlias) { + const ifTypeAlias = getGlobalIfTypeSymbol(); + if (!ifTypeAlias) { return errorType; } - return getTypeAliasInstantiation(condTypeAlias, [typeofTest, extendsType, type1, type2]); + return getTypeAliasInstantiation(ifTypeAlias, [typeofTest, extendsType, type1, type2]); } } } diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 4112f5a2ae7d7..489c55d7e9dc5 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1426,6 +1426,8 @@ type Pick = { [P in K]: T[P]; }; +type If = [C] extends [E] ? T : F; + /** * Construct a type with a set of properties K of type T */ From dab9944a8cefa956f852d2e02b710ccc26193054 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sat, 9 Mar 2019 00:35:15 +0000 Subject: [PATCH 12/21] Accept baselines --- .../reference/api/tsserverlibrary.d.ts | 6 +- tests/baselines/reference/api/typescript.d.ts | 6 +- .../reference/conditionalTypes1.errors.txt | 14 +- .../baselines/reference/conditionalTypes1.js | 22 ++-- .../reference/conditionalTypes1.symbols | 124 +++++++++--------- .../reference/conditionalTypes1.types | 24 ++-- .../types/conditional/conditionalTypes1.ts | 14 +- 7 files changed, 109 insertions(+), 101 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e99261be3f3b5..8101d283c8d9c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -563,6 +563,7 @@ declare namespace ts { constraint?: TypeNode; default?: TypeNode; expression?: Expression; + uniformityConstraint?: UniformityFlags; } interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SignatureDeclaration["kind"]; @@ -2277,6 +2278,9 @@ declare namespace ts { } interface EnumType extends Type { } + enum UniformityFlags { + TypeOf = 1 + } enum ObjectFlags { Class = 1, Interface = 2, @@ -3757,7 +3761,7 @@ declare namespace ts { function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName; function createComputedPropertyName(expression: Expression): ComputedPropertyName; function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName; - function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration; + function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration; function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration; function createParameter(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration; function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d7b494960537c..d8068a38d0682 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -563,6 +563,7 @@ declare namespace ts { constraint?: TypeNode; default?: TypeNode; expression?: Expression; + uniformityConstraint?: UniformityFlags; } interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SignatureDeclaration["kind"]; @@ -2277,6 +2278,9 @@ declare namespace ts { } interface EnumType extends Type { } + enum UniformityFlags { + TypeOf = 1 + } enum ObjectFlags { Class = 1, Interface = 2, @@ -3757,7 +3761,7 @@ declare namespace ts { function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName; function createComputedPropertyName(expression: Expression): ComputedPropertyName; function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName; - function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration; + function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration; function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration; function createParameter(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration; function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration; diff --git a/tests/baselines/reference/conditionalTypes1.errors.txt b/tests/baselines/reference/conditionalTypes1.errors.txt index ec35f89161589..647c96af646ab 100644 --- a/tests/baselines/reference/conditionalTypes1.errors.txt +++ b/tests/baselines/reference/conditionalTypes1.errors.txt @@ -290,10 +290,10 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS type T38 = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35 : never : never; type Extends = T extends U ? true : false; - type If = C extends true ? T : F; - type Not = If; - type And = If; - type Or = If; + type IfC = C extends true ? T : F; + type Not = IfC; + type And = IfC; + type Or = IfC; type IsString = Extends; @@ -420,9 +420,9 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS function f50() { type Eq = T extends U ? U extends T ? true : false : false; - type If = S extends false ? U : T; - type Omit = { [P in keyof T]: If, never, P>; }[keyof T]; - type Omit2 = { [P in keyof T]: If, never, P>; }[keyof T]; + type IfC = S extends false ? U : T; + type Omit = { [P in keyof T]: IfC, never, P>; }[keyof T]; + type Omit2 = { [P in keyof T]: IfC, never, P>; }[keyof T]; type A = Omit<{ a: void; b: never; }>; // 'a' type B = Omit2<{ a: void; b: never; }>; // 'a' } diff --git a/tests/baselines/reference/conditionalTypes1.js b/tests/baselines/reference/conditionalTypes1.js index b592bd33d10e8..faed16a136ac5 100644 --- a/tests/baselines/reference/conditionalTypes1.js +++ b/tests/baselines/reference/conditionalTypes1.js @@ -167,10 +167,10 @@ type T37 = T extends { b: number } ? T extends { a: string } ? T35 : never type T38 = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35 : never : never; type Extends = T extends U ? true : false; -type If = C extends true ? T : F; -type Not = If; -type And = If; -type Or = If; +type IfC = C extends true ? T : F; +type Not = IfC; +type And = IfC; +type Or = IfC; type IsString = Extends; @@ -292,9 +292,9 @@ const f45 = (value: T95): T94 => value; // Error function f50() { type Eq = T extends U ? U extends T ? true : false : false; - type If = S extends false ? U : T; - type Omit = { [P in keyof T]: If, never, P>; }[keyof T]; - type Omit2 = { [P in keyof T]: If, never, P>; }[keyof T]; + type IfC = S extends false ? U : T; + type Omit = { [P in keyof T]: IfC, never, P>; }[keyof T]; + type Omit2 = { [P in keyof T]: IfC, never, P>; }[keyof T]; type A = Omit<{ a: void; b: never; }>; // 'a' type B = Omit2<{ a: void; b: never; }>; // 'a' } @@ -591,10 +591,10 @@ declare type T38 = [T] extends [{ b: number; }] ? T35 : never : never; declare type Extends = T extends U ? true : false; -declare type If = C extends true ? T : F; -declare type Not = If; -declare type And = If; -declare type Or = If; +declare type IfC = C extends true ? T : F; +declare type Not = IfC; +declare type And = IfC; +declare type Or = IfC; declare type IsString = Extends; declare type Q1 = IsString; declare type Q2 = IsString<"abc">; diff --git a/tests/baselines/reference/conditionalTypes1.symbols b/tests/baselines/reference/conditionalTypes1.symbols index e5c6ae3d6c7f7..e4c9d215a2f08 100644 --- a/tests/baselines/reference/conditionalTypes1.symbols +++ b/tests/baselines/reference/conditionalTypes1.symbols @@ -656,142 +656,142 @@ type Extends = T extends U ? true : false; >T : Symbol(T, Decl(conditionalTypes1.ts, 167, 13)) >U : Symbol(U, Decl(conditionalTypes1.ts, 167, 15)) -type If = C extends true ? T : F; ->If : Symbol(If, Decl(conditionalTypes1.ts, 167, 48)) ->C : Symbol(C, Decl(conditionalTypes1.ts, 168, 8)) ->T : Symbol(T, Decl(conditionalTypes1.ts, 168, 26)) ->F : Symbol(F, Decl(conditionalTypes1.ts, 168, 29)) ->C : Symbol(C, Decl(conditionalTypes1.ts, 168, 8)) ->T : Symbol(T, Decl(conditionalTypes1.ts, 168, 26)) ->F : Symbol(F, Decl(conditionalTypes1.ts, 168, 29)) - -type Not = If; ->Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 58)) +type IfC = C extends true ? T : F; +>IfC : Symbol(IfC, Decl(conditionalTypes1.ts, 167, 48)) +>C : Symbol(C, Decl(conditionalTypes1.ts, 168, 9)) +>T : Symbol(T, Decl(conditionalTypes1.ts, 168, 27)) +>F : Symbol(F, Decl(conditionalTypes1.ts, 168, 30)) +>C : Symbol(C, Decl(conditionalTypes1.ts, 168, 9)) +>T : Symbol(T, Decl(conditionalTypes1.ts, 168, 27)) +>F : Symbol(F, Decl(conditionalTypes1.ts, 168, 30)) + +type Not = IfC; +>Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 59)) >C : Symbol(C, Decl(conditionalTypes1.ts, 169, 9)) ->If : Symbol(If, Decl(conditionalTypes1.ts, 167, 48)) +>IfC : Symbol(IfC, Decl(conditionalTypes1.ts, 167, 48)) >C : Symbol(C, Decl(conditionalTypes1.ts, 169, 9)) -type And = If; ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +type And = IfC; +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) >A : Symbol(A, Decl(conditionalTypes1.ts, 170, 9)) >B : Symbol(B, Decl(conditionalTypes1.ts, 170, 27)) ->If : Symbol(If, Decl(conditionalTypes1.ts, 167, 48)) +>IfC : Symbol(IfC, Decl(conditionalTypes1.ts, 167, 48)) >A : Symbol(A, Decl(conditionalTypes1.ts, 170, 9)) >B : Symbol(B, Decl(conditionalTypes1.ts, 170, 27)) -type Or = If; ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +type Or = IfC; +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) >A : Symbol(A, Decl(conditionalTypes1.ts, 171, 8)) >B : Symbol(B, Decl(conditionalTypes1.ts, 171, 26)) ->If : Symbol(If, Decl(conditionalTypes1.ts, 167, 48)) +>IfC : Symbol(IfC, Decl(conditionalTypes1.ts, 167, 48)) >A : Symbol(A, Decl(conditionalTypes1.ts, 171, 8)) >B : Symbol(B, Decl(conditionalTypes1.ts, 171, 26)) type IsString = Extends; ->IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 63)) +>IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 64)) >T : Symbol(T, Decl(conditionalTypes1.ts, 173, 14)) >Extends : Symbol(Extends, Decl(conditionalTypes1.ts, 165, 97)) >T : Symbol(T, Decl(conditionalTypes1.ts, 173, 14)) type Q1 = IsString; // false >Q1 : Symbol(Q1, Decl(conditionalTypes1.ts, 173, 38)) ->IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 63)) +>IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 64)) type Q2 = IsString<"abc">; // true >Q2 : Symbol(Q2, Decl(conditionalTypes1.ts, 175, 27)) ->IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 63)) +>IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 64)) type Q3 = IsString; // boolean >Q3 : Symbol(Q3, Decl(conditionalTypes1.ts, 176, 26)) ->IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 63)) +>IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 64)) type Q4 = IsString; // never >Q4 : Symbol(Q4, Decl(conditionalTypes1.ts, 177, 24)) ->IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 63)) +>IsString : Symbol(IsString, Decl(conditionalTypes1.ts, 171, 64)) type N1 = Not; // true >N1 : Symbol(N1, Decl(conditionalTypes1.ts, 178, 26)) ->Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 58)) +>Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 59)) type N2 = Not; // false >N2 : Symbol(N2, Decl(conditionalTypes1.ts, 180, 21)) ->Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 58)) +>Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 59)) type N3 = Not; // boolean >N3 : Symbol(N3, Decl(conditionalTypes1.ts, 181, 20)) ->Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 58)) +>Not : Symbol(Not, Decl(conditionalTypes1.ts, 168, 59)) type A1 = And; // false >A1 : Symbol(A1, Decl(conditionalTypes1.ts, 182, 23)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A2 = And; // false >A2 : Symbol(A2, Decl(conditionalTypes1.ts, 184, 28)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A3 = And; // false >A3 : Symbol(A3, Decl(conditionalTypes1.ts, 185, 27)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A4 = And; // true >A4 : Symbol(A4, Decl(conditionalTypes1.ts, 186, 27)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A5 = And; // false >A5 : Symbol(A5, Decl(conditionalTypes1.ts, 187, 26)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A6 = And; // false >A6 : Symbol(A6, Decl(conditionalTypes1.ts, 188, 30)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A7 = And; // boolean >A7 : Symbol(A7, Decl(conditionalTypes1.ts, 189, 30)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A8 = And; // boolean >A8 : Symbol(A8, Decl(conditionalTypes1.ts, 190, 29)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type A9 = And; // boolean >A9 : Symbol(A9, Decl(conditionalTypes1.ts, 191, 29)) ->And : Symbol(And, Decl(conditionalTypes1.ts, 169, 49)) +>And : Symbol(And, Decl(conditionalTypes1.ts, 169, 50)) type O1 = Or; // false >O1 : Symbol(O1, Decl(conditionalTypes1.ts, 192, 32)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O2 = Or; // true >O2 : Symbol(O2, Decl(conditionalTypes1.ts, 194, 27)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O3 = Or; // true >O3 : Symbol(O3, Decl(conditionalTypes1.ts, 195, 26)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O4 = Or; // true >O4 : Symbol(O4, Decl(conditionalTypes1.ts, 196, 26)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O5 = Or; // boolean >O5 : Symbol(O5, Decl(conditionalTypes1.ts, 197, 25)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O6 = Or; // boolean >O6 : Symbol(O6, Decl(conditionalTypes1.ts, 198, 29)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O7 = Or; // true >O7 : Symbol(O7, Decl(conditionalTypes1.ts, 199, 29)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O8 = Or; // true >O8 : Symbol(O8, Decl(conditionalTypes1.ts, 200, 28)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type O9 = Or; // boolean >O9 : Symbol(O9, Decl(conditionalTypes1.ts, 201, 28)) ->Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 65)) +>Or : Symbol(Or, Decl(conditionalTypes1.ts, 170, 66)) type T40 = never extends never ? true : false; // true >T40 : Symbol(T40, Decl(conditionalTypes1.ts, 202, 31)) @@ -1136,34 +1136,34 @@ function f50() { >U : Symbol(U, Decl(conditionalTypes1.ts, 292, 14)) >T : Symbol(T, Decl(conditionalTypes1.ts, 292, 12)) - type If = S extends false ? U : T; ->If : Symbol(If, Decl(conditionalTypes1.ts, 292, 69)) ->S : Symbol(S, Decl(conditionalTypes1.ts, 293, 12)) ->T : Symbol(T, Decl(conditionalTypes1.ts, 293, 14)) ->U : Symbol(U, Decl(conditionalTypes1.ts, 293, 17)) ->S : Symbol(S, Decl(conditionalTypes1.ts, 293, 12)) ->U : Symbol(U, Decl(conditionalTypes1.ts, 293, 17)) ->T : Symbol(T, Decl(conditionalTypes1.ts, 293, 14)) - - type Omit = { [P in keyof T]: If, never, P>; }[keyof T]; ->Omit : Symbol(Omit, Decl(conditionalTypes1.ts, 293, 47)) + type IfC = S extends false ? U : T; +>IfC : Symbol(IfC, Decl(conditionalTypes1.ts, 292, 69)) +>S : Symbol(S, Decl(conditionalTypes1.ts, 293, 13)) +>T : Symbol(T, Decl(conditionalTypes1.ts, 293, 15)) +>U : Symbol(U, Decl(conditionalTypes1.ts, 293, 18)) +>S : Symbol(S, Decl(conditionalTypes1.ts, 293, 13)) +>U : Symbol(U, Decl(conditionalTypes1.ts, 293, 18)) +>T : Symbol(T, Decl(conditionalTypes1.ts, 293, 15)) + + type Omit = { [P in keyof T]: IfC, never, P>; }[keyof T]; +>Omit : Symbol(Omit, Decl(conditionalTypes1.ts, 293, 48)) >T : Symbol(T, Decl(conditionalTypes1.ts, 294, 14)) >P : Symbol(P, Decl(conditionalTypes1.ts, 294, 37)) >T : Symbol(T, Decl(conditionalTypes1.ts, 294, 14)) ->If : Symbol(If, Decl(conditionalTypes1.ts, 292, 69)) +>IfC : Symbol(IfC, Decl(conditionalTypes1.ts, 292, 69)) >Eq : Symbol(Eq, Decl(conditionalTypes1.ts, 291, 16)) >T : Symbol(T, Decl(conditionalTypes1.ts, 294, 14)) >P : Symbol(P, Decl(conditionalTypes1.ts, 294, 37)) >P : Symbol(P, Decl(conditionalTypes1.ts, 294, 37)) >T : Symbol(T, Decl(conditionalTypes1.ts, 294, 14)) - type Omit2 = { [P in keyof T]: If, never, P>; }[keyof T]; ->Omit2 : Symbol(Omit2, Decl(conditionalTypes1.ts, 294, 94)) + type Omit2 = { [P in keyof T]: IfC, never, P>; }[keyof T]; +>Omit2 : Symbol(Omit2, Decl(conditionalTypes1.ts, 294, 95)) >T : Symbol(T, Decl(conditionalTypes1.ts, 295, 15)) >U : Symbol(U, Decl(conditionalTypes1.ts, 295, 32)) >P : Symbol(P, Decl(conditionalTypes1.ts, 295, 49)) >T : Symbol(T, Decl(conditionalTypes1.ts, 295, 15)) ->If : Symbol(If, Decl(conditionalTypes1.ts, 292, 69)) +>IfC : Symbol(IfC, Decl(conditionalTypes1.ts, 292, 69)) >Eq : Symbol(Eq, Decl(conditionalTypes1.ts, 291, 16)) >T : Symbol(T, Decl(conditionalTypes1.ts, 295, 15)) >P : Symbol(P, Decl(conditionalTypes1.ts, 295, 49)) @@ -1172,14 +1172,14 @@ function f50() { >T : Symbol(T, Decl(conditionalTypes1.ts, 295, 15)) type A = Omit<{ a: void; b: never; }>; // 'a' ->A : Symbol(A, Decl(conditionalTypes1.ts, 295, 102)) ->Omit : Symbol(Omit, Decl(conditionalTypes1.ts, 293, 47)) +>A : Symbol(A, Decl(conditionalTypes1.ts, 295, 103)) +>Omit : Symbol(Omit, Decl(conditionalTypes1.ts, 293, 48)) >a : Symbol(a, Decl(conditionalTypes1.ts, 296, 19)) >b : Symbol(b, Decl(conditionalTypes1.ts, 296, 28)) type B = Omit2<{ a: void; b: never; }>; // 'a' >B : Symbol(B, Decl(conditionalTypes1.ts, 296, 42)) ->Omit2 : Symbol(Omit2, Decl(conditionalTypes1.ts, 294, 94)) +>Omit2 : Symbol(Omit2, Decl(conditionalTypes1.ts, 294, 95)) >a : Symbol(a, Decl(conditionalTypes1.ts, 297, 20)) >b : Symbol(b, Decl(conditionalTypes1.ts, 297, 29)) } diff --git a/tests/baselines/reference/conditionalTypes1.types b/tests/baselines/reference/conditionalTypes1.types index 8cca94f0a1021..3d3fbfaec4fbb 100644 --- a/tests/baselines/reference/conditionalTypes1.types +++ b/tests/baselines/reference/conditionalTypes1.types @@ -518,21 +518,21 @@ type Extends = T extends U ? true : false; >true : true >false : false -type If = C extends true ? T : F; ->If : If +type IfC = C extends true ? T : F; +>IfC : IfC >true : true -type Not = If; ->Not : If +type Not = IfC; +>Not : IfC >false : false >true : true -type And = If; ->And : If +type And = IfC; +>And : IfC >false : false -type Or = If; ->Or : If +type Or = IfC; +>Or : IfC >true : true type IsString = Extends; @@ -909,14 +909,14 @@ function f50() { >false : false >false : false - type If = S extends false ? U : T; ->If : S extends false ? U : T + type IfC = S extends false ? U : T; +>IfC : S extends false ? U : T >false : false - type Omit = { [P in keyof T]: If, never, P>; }[keyof T]; + type Omit = { [P in keyof T]: IfC, never, P>; }[keyof T]; >Omit : { [P in keyof T]: (T[P] extends never ? never : false) extends false ? P : never; }[keyof T] - type Omit2 = { [P in keyof T]: If, never, P>; }[keyof T]; + type Omit2 = { [P in keyof T]: IfC, never, P>; }[keyof T]; >Omit2 : { [P in keyof T]: (T[P] extends U ? U extends T[P] ? true : false : false) extends false ? P : never; }[keyof T] type A = Omit<{ a: void; b: never; }>; // 'a' diff --git a/tests/cases/conformance/types/conditional/conditionalTypes1.ts b/tests/cases/conformance/types/conditional/conditionalTypes1.ts index 2e8690a228b45..91ccfec356e3b 100644 --- a/tests/cases/conformance/types/conditional/conditionalTypes1.ts +++ b/tests/cases/conformance/types/conditional/conditionalTypes1.ts @@ -169,10 +169,10 @@ type T37 = T extends { b: number } ? T extends { a: string } ? T35 : never type T38 = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35 : never : never; type Extends = T extends U ? true : false; -type If = C extends true ? T : F; -type Not = If; -type And = If; -type Or = If; +type IfC = C extends true ? T : F; +type Not = IfC; +type And = IfC; +type Or = IfC; type IsString = Extends; @@ -294,9 +294,9 @@ const f45 = (value: T95): T94 => value; // Error function f50() { type Eq = T extends U ? U extends T ? true : false : false; - type If = S extends false ? U : T; - type Omit = { [P in keyof T]: If, never, P>; }[keyof T]; - type Omit2 = { [P in keyof T]: If, never, P>; }[keyof T]; + type IfC = S extends false ? U : T; + type Omit = { [P in keyof T]: IfC, never, P>; }[keyof T]; + type Omit2 = { [P in keyof T]: IfC, never, P>; }[keyof T]; type A = Omit<{ a: void; b: never; }>; // 'a' type B = Omit2<{ a: void; b: never; }>; // 'a' } From 45b29db21ae1dbe1819c8e2baef5f7d86ab2e495 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sat, 9 Mar 2019 11:03:34 +0000 Subject: [PATCH 13/21] Fix emitter --- src/compiler/checker.ts | 7 ++++--- src/compiler/emitter.ts | 2 +- src/compiler/factory.ts | 5 +++-- src/compiler/transformers/declarations.ts | 2 +- src/compiler/visitor.ts | 3 ++- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f35baf96d3090..b34f34c14cd50 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3994,7 +3994,8 @@ namespace ts { const defaultParameter = getDefaultFromTypeParameter(type); const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); context.flags = savedContextFlags; - return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode, type.uniformityConstraint); + const uniformityConstraint = getUniformityConstraintDeclaration(type); + return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode, uniformityConstraint); } function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { @@ -16342,7 +16343,7 @@ namespace ts { } const targetType = getTypeOfExpression(target); const isNarrowableUnderUniformity = - (targetType.flags & TypeFlags.TypeVariable) && (getUniformityConstraintFromTypeParameter(targetType) & UniformityFlags.TypeOf); + (targetType.flags & TypeFlags.TypeParameter) && (getUniformityConstraintFromTypeParameter(targetType) & UniformityFlags.TypeOf); if (!isNarrowableUnderUniformity) { return type; } @@ -16356,7 +16357,7 @@ namespace ts { return type; } const subst = getIntersectionType([targetType, substType]); - const mapper = createTypeMapper([targetType], [subst]); + const mapper = createTypeMapper([targetType], [subst]); const narrowedMapper = combineTypeMappers(cond.mapper, mapper); return instantiateType(cond, narrowedMapper); } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 21acb7bc72a05..49f19a3d27c28 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1637,7 +1637,7 @@ namespace ts { function emitTypeParameter(node: TypeParameterDeclaration) { emit(node.name); - if (node.uniformityConstraint) { + if (node.uniformityConstraint && node.uniformityConstraint & UniformityFlags.TypeOf) { writePunctuation("!"); } if (node.constraint) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 4a7446808cedf..ded8151624151 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -309,11 +309,12 @@ namespace ts { return node; } - export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { + export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined, uniformityConstraint?: UniformityFlags) { return node.name !== name || node.constraint !== constraint || node.default !== defaultType - ? updateNode(createTypeParameterDeclaration(name, constraint, defaultType), node) + || node.uniformityConstraint !== uniformityConstraint + ? updateNode(createTypeParameterDeclaration(name, constraint, defaultType, uniformityConstraint), node) : node; } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 1f88bfb9e4f34..ee1d93866efe5 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -847,7 +847,7 @@ namespace ts { } case SyntaxKind.TypeParameter: { if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { - return cleanup(updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined)); + return cleanup(updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined, /*uniformityConstraint*/ undefined)); } return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 0d4d0d9482d6a..91bb2777dc59e 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -237,7 +237,8 @@ namespace ts { return updateTypeParameterDeclaration(node, visitNode((node).name, visitor, isIdentifier), visitNode((node).constraint, visitor, isTypeNode), - visitNode((node).default, visitor, isTypeNode)); + visitNode((node).default, visitor, isTypeNode), + (node).uniformityConstraint); case SyntaxKind.Parameter: return updateParameter(node, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 8101d283c8d9c..47be36b718ab2 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3762,7 +3762,7 @@ declare namespace ts { function createComputedPropertyName(expression: Expression): ComputedPropertyName; function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName; function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration; - function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration; + function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration; function createParameter(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration; function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration; function createDecorator(expression: Expression): Decorator; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d8068a38d0682..3269a9b938b94 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3762,7 +3762,7 @@ declare namespace ts { function createComputedPropertyName(expression: Expression): ComputedPropertyName; function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName; function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration; - function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration; + function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration; function createParameter(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration; function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration; function createDecorator(expression: Expression): Decorator; From e093a7d01f302a0f04b314475e3ca7639c98f108 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sat, 9 Mar 2019 11:27:34 +0000 Subject: [PATCH 14/21] Add If Completion --- src/harness/fourslash.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 16f1a37ae83fa..1d7452b8c140f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -4504,6 +4504,7 @@ namespace FourSlashInterface { typeEntry("Required"), typeEntry("Readonly"), typeEntry("Pick"), + typeEntry("If"), typeEntry("Record"), typeEntry("Exclude"), typeEntry("Extract"), From 2226e62a968ee0f2be873e00ec20a1a1e923b681 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sat, 9 Mar 2019 11:40:47 +0000 Subject: [PATCH 15/21] Fix lint --- src/compiler/checker.ts | 14 +++++++------- src/compiler/types.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b34f34c14cd50..79c6397afbeb1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8593,7 +8593,7 @@ namespace ts { } function getUniformityConstraintFromTypeParameter(typeParameter: TypeParameter): UniformityFlags { - if(typeParameter.uniformityConstraint === undefined) { + if (typeParameter.uniformityConstraint === undefined) { typeParameter.uniformityConstraint = getUniformityConstraintDeclaration(typeParameter); } return typeParameter.uniformityConstraint; @@ -15064,7 +15064,7 @@ namespace ts { const uniformityConstraint = getUniformityConstraintFromTypeParameter(inference.typeParameter); if (uniformityConstraint) { if (!isUniformType(inferredType)) { - error(undefined, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType), typeToString(inference.typeParameter)); + error(/*location*/ undefined, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType), typeToString(inference.typeParameter)); } } if (constraint) { @@ -16338,7 +16338,7 @@ namespace ts { if (containsMatchingReference(reference, target)) { return declaredType; } - if(!isIdentifier(target)) { + if (!isIdentifier(target)) { return type; } const targetType = getTypeOfExpression(target); @@ -20325,11 +20325,11 @@ namespace ts { return true; } if (type.flags & TypeFlags.TypeParameter) { - return !!getUniformityConstraintFromTypeParameter( type); + return !!getUniformityConstraintFromTypeParameter(type); } - return false; + return false; } - + function checkTypeArguments(signature: Signature, typeArgumentNodes: ReadonlyArray, reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { const isJavascript = isInJSFile(signature.declaration); const typeParameters = signature.typeParameters!; @@ -23370,7 +23370,7 @@ namespace ts { const extendsType = typeofTypesByName.get(node.condition.right.text); if (extendsType !== undefined) { const typeofTest = getTypeOfExpression((node.condition.left).expression); - if((typeofTest.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(typeofTest)) { + if ((typeofTest.flags & TypeFlags.TypeVariable) && getUniformityConstraintFromTypeParameter(typeofTest)) { const ifTypeAlias = getGlobalIfTypeSymbol(); if (!ifTypeAlias) { return errorType; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0d39eae1534fe..d4a21a70edc41 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4046,7 +4046,7 @@ namespace ts { export const enum UniformityFlags { TypeOf = 1 << 0, } - + export const enum ObjectFlags { Class = 1 << 0, // Class Interface = 1 << 1, // Interface From c055d734f9d2b7ef0c9f2bcfb7d88806c84687e4 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sat, 9 Mar 2019 14:50:26 +0000 Subject: [PATCH 16/21] Better propagation --- src/compiler/checker.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 79c6397afbeb1..ee01b3f5cfd02 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16329,6 +16329,16 @@ namespace ts { return type; } + function isTypeNarrowableUnderUniformity(type: Type): boolean { + if (type.flags & TypeFlags.TypeParameter) { + return (getUniformityConstraintFromTypeParameter(type) & UniformityFlags.TypeOf) !== 0; + } + if (type.flags & TypeFlags.Intersection) { + return (type).types.some(isTypeNarrowableUnderUniformity); + } + return false; + } + function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands const target = getReferenceCandidate(typeOfExpr.expression); @@ -16342,8 +16352,7 @@ namespace ts { return type; } const targetType = getTypeOfExpression(target); - const isNarrowableUnderUniformity = - (targetType.flags & TypeFlags.TypeParameter) && (getUniformityConstraintFromTypeParameter(targetType) & UniformityFlags.TypeOf); + const isNarrowableUnderUniformity = isTypeNarrowableUnderUniformity(targetType); if (!isNarrowableUnderUniformity) { return type; } @@ -16361,6 +16370,14 @@ namespace ts { const narrowedMapper = combineTypeMappers(cond.mapper, mapper); return instantiateType(cond, narrowedMapper); } + if (assumeTrue && targetType === type) { + const substType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text); + if (substType) { + const narrowed = getIntersectionType([type, substType]); + return (narrowed.flags & TypeFlags.Intersection) && isEmptyIntersectionType(narrowed) ? neverType : narrowed; + } + return declaredType; + } if (targetType !== type) { return declaredType; } @@ -16397,6 +16414,10 @@ namespace ts { return getIntersectionType([type, targetType]); } } + if (isTypeNarrowableUnderUniformity(type)) { + const narrowed = getIntersectionType([type, targetType]); + return (narrowed.flags & TypeFlags.Intersection) && isEmptyIntersectionType(narrowed) ? neverType : narrowed; + } } return type; } From e9e7673f7c5b4ecd5450d6a2770db6b41f9da8c5 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sat, 9 Mar 2019 14:59:28 +0000 Subject: [PATCH 17/21] Remove duplicate code --- src/compiler/checker.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ee01b3f5cfd02..079e887a9f0e9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16370,14 +16370,6 @@ namespace ts { const narrowedMapper = combineTypeMappers(cond.mapper, mapper); return instantiateType(cond, narrowedMapper); } - if (assumeTrue && targetType === type) { - const substType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text); - if (substType) { - const narrowed = getIntersectionType([type, substType]); - return (narrowed.flags & TypeFlags.Intersection) && isEmptyIntersectionType(narrowed) ? neverType : narrowed; - } - return declaredType; - } if (targetType !== type) { return declaredType; } From e6a4b072fd56ce082307b09265b24172dc18b1cd Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 10 Mar 2019 12:02:06 +0000 Subject: [PATCH 18/21] Add naive equality case --- src/compiler/checker.ts | 80 ++++++++++++++++++++++++++++++++--------- src/compiler/emitter.ts | 3 ++ src/compiler/parser.ts | 1 + src/compiler/types.ts | 1 + 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 079e887a9f0e9..47824a2df43ac 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15063,7 +15063,7 @@ namespace ts { const constraint = getConstraintOfTypeParameter(inference.typeParameter); const uniformityConstraint = getUniformityConstraintFromTypeParameter(inference.typeParameter); if (uniformityConstraint) { - if (!isUniformType(inferredType)) { + if (!isUniformType(inferredType, uniformityConstraint)) { error(/*location*/ undefined, Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(inferredType), typeToString(inference.typeParameter)); } } @@ -16271,7 +16271,8 @@ namespace ts { if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { return declaredType; } - break; + return narrowTypeByUniformEquality(type, operator, left, right, assumeTrue); + case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: @@ -16286,6 +16287,45 @@ namespace ts { return type; } + function narrowTypeByUniformEquality(type: Type, operator: SyntaxKind, left: Expression, right: Expression, assumeTrue: boolean): Type { + if ((operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { + assumeTrue = !assumeTrue + } + if (!assumeTrue || operator === SyntaxKind.EqualsEqualsToken) { + return type; + } + const leftType = getTypeOfExpression(left); + const rightType = getTypeOfExpression(right); + const leftIsNarrowable = isTypeNarrowableUnderUniformity(leftType, UniformityFlags.Equality | UniformityFlags.TypeOf); + if (leftIsNarrowable && isUnitType(rightType)) { + if (leftType === type) { + return getIntersectionType([type, rightType]); + } + if (type.flags & TypeFlags.Conditional) { + const cond = type; + const subst = getIntersectionType([leftType, rightType]); + const mapper = createTypeMapper([leftType], [subst]); + const narrowedMapper = combineTypeMappers(cond.mapper, mapper); + return instantiateType(cond, narrowedMapper); + } + return type; + } + const rightIsNarrowable = isTypeNarrowableUnderUniformity(rightType, UniformityFlags.Equality | UniformityFlags.TypeOf); + if (rightIsNarrowable && isUnitType(leftType)) { + if (rightType === type) { + return getIntersectionType([type, leftType]); + } + if (type.flags & TypeFlags.Conditional) { + const cond = type; + const subst = getIntersectionType([rightType, leftType]); + const mapper = createTypeMapper([rightType], [subst]); + const narrowedMapper = combineTypeMappers(cond.mapper, mapper); + return instantiateType(cond, narrowedMapper); + } + } + return type; + } + function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { if (type.flags & TypeFlags.Any) { return type; @@ -16329,12 +16369,12 @@ namespace ts { return type; } - function isTypeNarrowableUnderUniformity(type: Type): boolean { + function isTypeNarrowableUnderUniformity(type: Type, kind: UniformityFlags): boolean { if (type.flags & TypeFlags.TypeParameter) { - return (getUniformityConstraintFromTypeParameter(type) & UniformityFlags.TypeOf) !== 0; + return (getUniformityConstraintFromTypeParameter(type) & kind) !== 0; } if (type.flags & TypeFlags.Intersection) { - return (type).types.some(isTypeNarrowableUnderUniformity); + return (type).types.some(t => isTypeNarrowableUnderUniformity(t, kind)); } return false; } @@ -16352,12 +16392,15 @@ namespace ts { return type; } const targetType = getTypeOfExpression(target); - const isNarrowableUnderUniformity = isTypeNarrowableUnderUniformity(targetType); + const isNarrowableUnderUniformity = isTypeNarrowableUnderUniformity(targetType, UniformityFlags.TypeOf); if (!isNarrowableUnderUniformity) { return type; } if (type.flags & TypeFlags.Conditional) { - if (!assumeTrue || operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + if ((operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { + assumeTrue = !assumeTrue + } + if (!assumeTrue || operator === SyntaxKind.EqualsEqualsToken) { return type; } const cond = type; @@ -16406,7 +16449,7 @@ namespace ts { return getIntersectionType([type, targetType]); } } - if (isTypeNarrowableUnderUniformity(type)) { + if (isTypeNarrowableUnderUniformity(type, UniformityFlags.TypeOf)) { const narrowed = getIntersectionType([type, targetType]); return (narrowed.flags & TypeFlags.Intersection) && isEmptyIntersectionType(narrowed) ? neverType : narrowed; } @@ -20332,13 +20375,18 @@ namespace ts { createTupleType(append(types.slice(0, spreadIndex), getUnionType(types.slice(spreadIndex))), spreadIndex, /*hasRestElement*/ true); } - function isUniformType(type: Type): boolean { - // jw todo: more cases - if (isUnitType(type) || (type.flags & TypeFlags.Primitive)) { - return true; + function isUniformType(type: Type, kind: UniformityFlags): boolean { + if (kind & UniformityFlags.Equality) { + return isUnitType(type); } - if (type.flags & TypeFlags.TypeParameter) { - return !!getUniformityConstraintFromTypeParameter(type); + else if (kind & UniformityFlags.TypeOf) { + // jw todo: more cases + if (isUnitType(type) || (type.flags & TypeFlags.Primitive)) { + return true; + } + if (type.flags & TypeFlags.TypeParameter) { + return !!getUniformityConstraintFromTypeParameter(type); + } } return false; } @@ -20353,7 +20401,7 @@ namespace ts { const constraint = getConstraintOfTypeParameter(typeParameters[i]); const uniformityConstraint = getUniformityConstraintFromTypeParameter(typeParameters[i]); if (uniformityConstraint) { - if (!isUniformType(typeArgumentTypes[i])) { + if (!isUniformType(typeArgumentTypes[i], uniformityConstraint)) { error(typeArgumentNodes[i], Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArgumentTypes[i]), typeToString(typeParameters[i])); } } @@ -24451,7 +24499,7 @@ namespace ts { typeArguments = getEffectiveTypeArguments(node, typeParameters); mapper = createTypeMapper(typeParameters, typeArguments); } - if (!isUniformType(typeArguments[i])) { + if (!isUniformType(typeArguments[i], uniformityConstraint)) { error(node.typeArguments && node.typeArguments[i], Diagnostics.Type_0_does_not_satisfy_uniformity_constraint_of_type_1_Values_of_type_0_do_not_behave_identically_under_typeof, typeToString(typeArguments[i]), typeToString(typeParameters[i])); } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 49f19a3d27c28..a2d87a7bfd3fb 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1640,6 +1640,9 @@ namespace ts { if (node.uniformityConstraint && node.uniformityConstraint & UniformityFlags.TypeOf) { writePunctuation("!"); } + else if (node.uniformityConstraint && node.uniformityConstraint & UniformityFlags.Equality) { + writePunctuation("~"); + } if (node.constraint) { writeSpace(); writeKeyword("extends"); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6d7224d78e68d..73fd0d89a8411 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2417,6 +2417,7 @@ namespace ts { node.name = parseIdentifier(); node.uniformityConstraint = 0; node.uniformityConstraint |= parseOptional(SyntaxKind.ExclamationToken) ? UniformityFlags.TypeOf : 0; + node.uniformityConstraint |= parseOptional(SyntaxKind.TildeToken) ? UniformityFlags.Equality : 0; if (parseOptional(SyntaxKind.ExtendsKeyword)) { // It's not uncommon for people to write improper constraints to a generic. If the // user writes a constraint that is an expression and not an actual type, then parse diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d4a21a70edc41..f6942b287dffb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4045,6 +4045,7 @@ namespace ts { export const enum UniformityFlags { TypeOf = 1 << 0, + Equality = 1 << 1, } export const enum ObjectFlags { From 241713880c335fdba9365d3107410304c284d596 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 10 Mar 2019 12:04:17 +0000 Subject: [PATCH 19/21] Lint --- src/compiler/checker.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 47824a2df43ac..d06a12f06d609 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16272,7 +16272,7 @@ namespace ts { return declaredType; } return narrowTypeByUniformEquality(type, operator, left, right, assumeTrue); - + case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: @@ -16289,7 +16289,7 @@ namespace ts { function narrowTypeByUniformEquality(type: Type, operator: SyntaxKind, left: Expression, right: Expression, assumeTrue: boolean): Type { if ((operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { - assumeTrue = !assumeTrue + assumeTrue = !assumeTrue; } if (!assumeTrue || operator === SyntaxKind.EqualsEqualsToken) { return type; @@ -16325,7 +16325,7 @@ namespace ts { } return type; } - + function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { if (type.flags & TypeFlags.Any) { return type; @@ -16398,7 +16398,7 @@ namespace ts { } if (type.flags & TypeFlags.Conditional) { if ((operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { - assumeTrue = !assumeTrue + assumeTrue = !assumeTrue; } if (!assumeTrue || operator === SyntaxKind.EqualsEqualsToken) { return type; From 1c08f4197a4d2a1ad3cda8c2befdfabeec8236e7 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 10 Mar 2019 12:28:16 +0000 Subject: [PATCH 20/21] Accept baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 3 ++- tests/baselines/reference/api/typescript.d.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 47be36b718ab2..04e8cc2db8080 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2279,7 +2279,8 @@ declare namespace ts { interface EnumType extends Type { } enum UniformityFlags { - TypeOf = 1 + TypeOf = 1, + Equality = 2 } enum ObjectFlags { Class = 1, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3269a9b938b94..75a519b37f972 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2279,7 +2279,8 @@ declare namespace ts { interface EnumType extends Type { } enum UniformityFlags { - TypeOf = 1 + TypeOf = 1, + Equality = 2 } enum ObjectFlags { Class = 1, From 0b23e9f3e516965faa91c1c4e414f6cf8d11fbd4 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 10 Mar 2019 14:09:20 +0000 Subject: [PATCH 21/21] Hack conditional type resolution --- src/compiler/checker.ts | 42 +++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d06a12f06d609..53ef034e6af9b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10195,9 +10195,18 @@ namespace ts { // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. - if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) { - return falseType; + if (isTypeNarrowableUnderUniformity(checkType, UniformityFlags.Equality | UniformityFlags.TypeOf)) { + if (!isTypeAssignableTo(getUniformInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) { + return falseType; + } } + else { + if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) { + return falseType; + } + } + + // Return trueType for a definitely true extends check. We check instantiations of the two // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter // that has no constraint. This ensures that, for example, the type @@ -10830,6 +10839,10 @@ namespace ts { return type.flags & TypeFlags.TypeParameter ? wildcardType : type; } + function unknownMapper(type: Type) { + return type.flags & TypeFlags.TypeParameter ? unknownType : type; + } + function getRestrictiveTypeParameter(tp: TypeParameter) { return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || ( tp.restrictiveInstantiation = createTypeParameter(tp.symbol), @@ -11208,6 +11221,11 @@ namespace ts { type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); } + function getUniformInstantiation(type: Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, unknownMapper)); + } + function getRestrictiveInstantiation(type: Type) { if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { return type; @@ -13927,6 +13945,16 @@ namespace ts { return value.base10Value === "0"; } + function isTypeNarrowableUnderUniformity(type: Type, kind: UniformityFlags): boolean { + if (type.flags & TypeFlags.TypeParameter) { + return (getUniformityConstraintFromTypeParameter(type) & kind) !== 0; + } + if (type.flags & TypeFlags.Intersection) { + return (type).types.some(t => isTypeNarrowableUnderUniformity(t, kind)); + } + return false; + } + function getFalsyFlagsOfTypes(types: Type[]): TypeFlags { let result: TypeFlags = 0; for (const t of types) { @@ -16369,16 +16397,6 @@ namespace ts { return type; } - function isTypeNarrowableUnderUniformity(type: Type, kind: UniformityFlags): boolean { - if (type.flags & TypeFlags.TypeParameter) { - return (getUniformityConstraintFromTypeParameter(type) & kind) !== 0; - } - if (type.flags & TypeFlags.Intersection) { - return (type).types.some(t => isTypeNarrowableUnderUniformity(t, kind)); - } - return false; - } - function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands const target = getReferenceCandidate(typeOfExpr.expression);