Merge pull request #6947 from Microsoft/operatorsAndIntersections

Allow operators and indexing with intersections involving primtive types
This commit is contained in:
Anders Hejlsberg
2016-02-09 06:15:50 -08:00
5 changed files with 360 additions and 33 deletions
+41 -33
View File
@@ -5591,7 +5591,7 @@ namespace ts {
}
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
if (!(target.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties) && someConstituentTypeHasKind(target, TypeFlags.ObjectType)) {
if (!(target.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties) && maybeTypeOfKind(target, TypeFlags.ObjectType)) {
for (const prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
if (reportErrors) {
@@ -8178,7 +8178,7 @@ namespace ts {
}
function isTypeAnyOrAllConstituentTypesHaveKind(type: Type, kind: TypeFlags): boolean {
return isTypeAny(type) || allConstituentTypesHaveKind(type, kind);
return isTypeAny(type) || isTypeOfKind(type, kind);
}
function isNumericLiteralName(name: string) {
@@ -9684,7 +9684,7 @@ namespace ts {
case SyntaxKind.ComputedPropertyName:
const nameType = checkComputedPropertyName(<ComputedPropertyName>element.name);
if (allConstituentTypesHaveKind(nameType, TypeFlags.ESSymbol)) {
if (isTypeOfKind(nameType, TypeFlags.ESSymbol)) {
return nameType;
}
else {
@@ -10337,9 +10337,8 @@ namespace ts {
const widenedType = getWidenedType(exprType);
// Permit 'number[] | "foo"' to be asserted to 'string'.
const bothAreStringLike =
someConstituentTypeHasKind(targetType, TypeFlags.StringLike) &&
someConstituentTypeHasKind(widenedType, TypeFlags.StringLike);
const bothAreStringLike = maybeTypeOfKind(targetType, TypeFlags.StringLike) &&
maybeTypeOfKind(widenedType, TypeFlags.StringLike);
if (!bothAreStringLike && !(isTypeAssignableTo(targetType, widenedType))) {
checkTypeAssignableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other);
}
@@ -10594,7 +10593,7 @@ namespace ts {
}
// Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions.
if (returnType === voidType || isTypeAny(returnType) || (returnType && (returnType.flags & TypeFlags.Union) && someConstituentTypeHasKind(returnType, TypeFlags.Any | TypeFlags.Void))) {
if (returnType && maybeTypeOfKind(returnType, TypeFlags.Any | TypeFlags.Void)) {
return;
}
@@ -10850,7 +10849,7 @@ namespace ts {
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.TildeToken:
if (someConstituentTypeHasKind(operandType, TypeFlags.ESSymbol)) {
if (maybeTypeOfKind(operandType, TypeFlags.ESSymbol)) {
error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator));
}
return numberType;
@@ -10882,38 +10881,47 @@ namespace ts {
return numberType;
}
// Just like isTypeOfKind below, except that it returns true if *any* constituent
// has this kind.
function someConstituentTypeHasKind(type: Type, kind: TypeFlags): boolean {
// Return true if type might be of the given kind. A union or intersection type might be of a given
// kind if at least one constituent type is of the given kind.
function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean {
if (type.flags & kind) {
return true;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
const types = (<UnionOrIntersectionType>type).types;
for (const current of types) {
if (current.flags & kind) {
for (const t of types) {
if (maybeTypeOfKind(t, kind)) {
return true;
}
}
return false;
}
return false;
}
// Return true if type has the given flags, or is a union or intersection type composed of types that all have those flags.
function allConstituentTypesHaveKind(type: Type, kind: TypeFlags): boolean {
// Return true if type is of the given kind. A union type is of a given kind if all constituent types
// are of the given kind. An intersection type is of a given kind if at least one constituent type is
// of the given kind.
function isTypeOfKind(type: Type, kind: TypeFlags): boolean {
if (type.flags & kind) {
return true;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
if (type.flags & TypeFlags.Union) {
const types = (<UnionOrIntersectionType>type).types;
for (const current of types) {
if (!(current.flags & kind)) {
for (const t of types) {
if (!isTypeOfKind(t, kind)) {
return false;
}
}
return true;
}
if (type.flags & TypeFlags.Intersection) {
const types = (<UnionOrIntersectionType>type).types;
for (const t of types) {
if (isTypeOfKind(t, kind)) {
return true;
}
}
}
return false;
}
@@ -10931,7 +10939,7 @@ namespace ts {
// and the right operand to be of type Any or a subtype of the 'Function' interface type.
// The result is always of the Boolean primitive type.
// NOTE: do not raise error if leftType is unknown as related error was already reported
if (allConstituentTypesHaveKind(leftType, TypeFlags.Primitive)) {
if (isTypeOfKind(leftType, TypeFlags.Primitive)) {
error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
}
// NOTE: do not raise error if right is unknown as related error was already reported
@@ -11146,13 +11154,13 @@ namespace ts {
if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType;
let resultType: Type;
if (allConstituentTypesHaveKind(leftType, TypeFlags.NumberLike) && allConstituentTypesHaveKind(rightType, TypeFlags.NumberLike)) {
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) {
// Operands of an enum type are treated as having the primitive type Number.
// If both operands are of the Number primitive type, the result is of the Number primitive type.
resultType = numberType;
}
else {
if (allConstituentTypesHaveKind(leftType, TypeFlags.StringLike) || allConstituentTypesHaveKind(rightType, TypeFlags.StringLike)) {
if (isTypeOfKind(leftType, TypeFlags.StringLike) || isTypeOfKind(rightType, TypeFlags.StringLike)) {
// If one or both operands are of the String primitive type, the result is of the String primitive type.
resultType = stringType;
}
@@ -11190,7 +11198,7 @@ namespace ts {
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
// Permit 'number[] | "foo"' to be asserted to 'string'.
if (someConstituentTypeHasKind(leftType, TypeFlags.StringLike) && someConstituentTypeHasKind(rightType, TypeFlags.StringLike)) {
if (maybeTypeOfKind(leftType, TypeFlags.StringLike) && maybeTypeOfKind(rightType, TypeFlags.StringLike)) {
return booleanType;
}
if (!isTypeAssignableTo(leftType, rightType) && !isTypeAssignableTo(rightType, leftType)) {
@@ -11215,8 +11223,8 @@ namespace ts {
// Return true if there was no error, false if there was an error.
function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean {
const offendingSymbolOperand =
someConstituentTypeHasKind(leftType, TypeFlags.ESSymbol) ? left :
someConstituentTypeHasKind(rightType, TypeFlags.ESSymbol) ? right :
maybeTypeOfKind(leftType, TypeFlags.ESSymbol) ? left :
maybeTypeOfKind(rightType, TypeFlags.ESSymbol) ? right :
undefined;
if (offendingSymbolOperand) {
error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator));
@@ -13700,7 +13708,7 @@ namespace ts {
let hasDuplicateDefaultClause = false;
const expressionType = checkExpression(node.expression);
const expressionTypeIsStringLike = someConstituentTypeHasKind(expressionType, TypeFlags.StringLike);
const expressionTypeIsStringLike = maybeTypeOfKind(expressionType, TypeFlags.StringLike);
forEach(node.caseBlock.clauses, clause => {
// Grammar check for duplicate default clauses, skip if we already report duplicate default clause
if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) {
@@ -13724,7 +13732,7 @@ namespace ts {
const expressionTypeIsAssignableToCaseType =
// Permit 'number[] | "foo"' to be asserted to 'string'.
(expressionTypeIsStringLike && someConstituentTypeHasKind(caseType, TypeFlags.StringLike)) ||
(expressionTypeIsStringLike && maybeTypeOfKind(caseType, TypeFlags.StringLike)) ||
isTypeAssignableTo(expressionType, caseType);
if (!expressionTypeIsAssignableToCaseType) {
@@ -15930,22 +15938,22 @@ namespace ts {
else if (type.flags & TypeFlags.Any) {
return TypeReferenceSerializationKind.ObjectType;
}
else if (allConstituentTypesHaveKind(type, TypeFlags.Void)) {
else if (isTypeOfKind(type, TypeFlags.Void)) {
return TypeReferenceSerializationKind.VoidType;
}
else if (allConstituentTypesHaveKind(type, TypeFlags.Boolean)) {
else if (isTypeOfKind(type, TypeFlags.Boolean)) {
return TypeReferenceSerializationKind.BooleanType;
}
else if (allConstituentTypesHaveKind(type, TypeFlags.NumberLike)) {
else if (isTypeOfKind(type, TypeFlags.NumberLike)) {
return TypeReferenceSerializationKind.NumberLikeType;
}
else if (allConstituentTypesHaveKind(type, TypeFlags.StringLike)) {
else if (isTypeOfKind(type, TypeFlags.StringLike)) {
return TypeReferenceSerializationKind.StringLikeType;
}
else if (allConstituentTypesHaveKind(type, TypeFlags.Tuple)) {
else if (isTypeOfKind(type, TypeFlags.Tuple)) {
return TypeReferenceSerializationKind.ArrayLikeType;
}
else if (allConstituentTypesHaveKind(type, TypeFlags.ESSymbol)) {
else if (isTypeOfKind(type, TypeFlags.ESSymbol)) {
return TypeReferenceSerializationKind.ESSymbolType;
}
else if (isFunctionType(type)) {
@@ -0,0 +1,57 @@
//// [operatorsAndIntersectionTypes.ts]
type Guid = string & { $Guid }; // Tagged string type
type SerialNo = number & { $SerialNo }; // Tagged number type
function createGuid() {
return "21EC2020-3AEA-4069-A2DD-08002B30309D" as Guid;
}
function createSerialNo() {
return 12345 as SerialNo;
}
let map1: { [x: string]: number } = {};
let guid = createGuid();
map1[guid] = 123; // Can with tagged string
let map2: { [x: number]: string } = {};
let serialNo = createSerialNo();
map2[serialNo] = "hello"; // Can index with tagged number
const s1 = "{" + guid + "}";
const s2 = guid.toLowerCase();
const s3 = guid + guid;
const s4 = guid + serialNo;
const s5 = serialNo.toPrecision(0);
const n1 = serialNo * 3;
const n2 = serialNo + serialNo;
const b1 = guid === "";
const b2 = guid === guid;
const b3 = serialNo === 0;
const b4 = serialNo === serialNo;
//// [operatorsAndIntersectionTypes.js]
function createGuid() {
return "21EC2020-3AEA-4069-A2DD-08002B30309D";
}
function createSerialNo() {
return 12345;
}
var map1 = {};
var guid = createGuid();
map1[guid] = 123; // Can with tagged string
var map2 = {};
var serialNo = createSerialNo();
map2[serialNo] = "hello"; // Can index with tagged number
var s1 = "{" + guid + "}";
var s2 = guid.toLowerCase();
var s3 = guid + guid;
var s4 = guid + serialNo;
var s5 = serialNo.toPrecision(0);
var n1 = serialNo * 3;
var n2 = serialNo + serialNo;
var b1 = guid === "";
var b2 = guid === guid;
var b3 = serialNo === 0;
var b4 = serialNo === serialNo;
@@ -0,0 +1,100 @@
=== tests/cases/conformance/types/intersection/operatorsAndIntersectionTypes.ts ===
type Guid = string & { $Guid }; // Tagged string type
>Guid : Symbol(Guid, Decl(operatorsAndIntersectionTypes.ts, 0, 0))
>$Guid : Symbol($Guid, Decl(operatorsAndIntersectionTypes.ts, 0, 22))
type SerialNo = number & { $SerialNo }; // Tagged number type
>SerialNo : Symbol(SerialNo, Decl(operatorsAndIntersectionTypes.ts, 0, 31))
>$SerialNo : Symbol($SerialNo, Decl(operatorsAndIntersectionTypes.ts, 1, 26))
function createGuid() {
>createGuid : Symbol(createGuid, Decl(operatorsAndIntersectionTypes.ts, 1, 39))
return "21EC2020-3AEA-4069-A2DD-08002B30309D" as Guid;
>Guid : Symbol(Guid, Decl(operatorsAndIntersectionTypes.ts, 0, 0))
}
function createSerialNo() {
>createSerialNo : Symbol(createSerialNo, Decl(operatorsAndIntersectionTypes.ts, 5, 1))
return 12345 as SerialNo;
>SerialNo : Symbol(SerialNo, Decl(operatorsAndIntersectionTypes.ts, 0, 31))
}
let map1: { [x: string]: number } = {};
>map1 : Symbol(map1, Decl(operatorsAndIntersectionTypes.ts, 11, 3))
>x : Symbol(x, Decl(operatorsAndIntersectionTypes.ts, 11, 13))
let guid = createGuid();
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
>createGuid : Symbol(createGuid, Decl(operatorsAndIntersectionTypes.ts, 1, 39))
map1[guid] = 123; // Can with tagged string
>map1 : Symbol(map1, Decl(operatorsAndIntersectionTypes.ts, 11, 3))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
let map2: { [x: number]: string } = {};
>map2 : Symbol(map2, Decl(operatorsAndIntersectionTypes.ts, 15, 3))
>x : Symbol(x, Decl(operatorsAndIntersectionTypes.ts, 15, 13))
let serialNo = createSerialNo();
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
>createSerialNo : Symbol(createSerialNo, Decl(operatorsAndIntersectionTypes.ts, 5, 1))
map2[serialNo] = "hello"; // Can index with tagged number
>map2 : Symbol(map2, Decl(operatorsAndIntersectionTypes.ts, 15, 3))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
const s1 = "{" + guid + "}";
>s1 : Symbol(s1, Decl(operatorsAndIntersectionTypes.ts, 19, 5))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
const s2 = guid.toLowerCase();
>s2 : Symbol(s2, Decl(operatorsAndIntersectionTypes.ts, 20, 5))
>guid.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
const s3 = guid + guid;
>s3 : Symbol(s3, Decl(operatorsAndIntersectionTypes.ts, 21, 5))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
const s4 = guid + serialNo;
>s4 : Symbol(s4, Decl(operatorsAndIntersectionTypes.ts, 22, 5))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
const s5 = serialNo.toPrecision(0);
>s5 : Symbol(s5, Decl(operatorsAndIntersectionTypes.ts, 23, 5))
>serialNo.toPrecision : Symbol(Number.toPrecision, Decl(lib.d.ts, --, --))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
>toPrecision : Symbol(Number.toPrecision, Decl(lib.d.ts, --, --))
const n1 = serialNo * 3;
>n1 : Symbol(n1, Decl(operatorsAndIntersectionTypes.ts, 24, 5))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
const n2 = serialNo + serialNo;
>n2 : Symbol(n2, Decl(operatorsAndIntersectionTypes.ts, 25, 5))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
const b1 = guid === "";
>b1 : Symbol(b1, Decl(operatorsAndIntersectionTypes.ts, 26, 5))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
const b2 = guid === guid;
>b2 : Symbol(b2, Decl(operatorsAndIntersectionTypes.ts, 27, 5))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
>guid : Symbol(guid, Decl(operatorsAndIntersectionTypes.ts, 12, 3))
const b3 = serialNo === 0;
>b3 : Symbol(b3, Decl(operatorsAndIntersectionTypes.ts, 28, 5))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
const b4 = serialNo === serialNo;
>b4 : Symbol(b4, Decl(operatorsAndIntersectionTypes.ts, 29, 5))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
>serialNo : Symbol(serialNo, Decl(operatorsAndIntersectionTypes.ts, 16, 3))
@@ -0,0 +1,132 @@
=== tests/cases/conformance/types/intersection/operatorsAndIntersectionTypes.ts ===
type Guid = string & { $Guid }; // Tagged string type
>Guid : string & { $Guid: any; }
>$Guid : any
type SerialNo = number & { $SerialNo }; // Tagged number type
>SerialNo : number & { $SerialNo: any; }
>$SerialNo : any
function createGuid() {
>createGuid : () => string & { $Guid: any; }
return "21EC2020-3AEA-4069-A2DD-08002B30309D" as Guid;
>"21EC2020-3AEA-4069-A2DD-08002B30309D" as Guid : string & { $Guid: any; }
>"21EC2020-3AEA-4069-A2DD-08002B30309D" : string
>Guid : string & { $Guid: any; }
}
function createSerialNo() {
>createSerialNo : () => number & { $SerialNo: any; }
return 12345 as SerialNo;
>12345 as SerialNo : number & { $SerialNo: any; }
>12345 : number
>SerialNo : number & { $SerialNo: any; }
}
let map1: { [x: string]: number } = {};
>map1 : { [x: string]: number; }
>x : string
>{} : { [x: string]: undefined; }
let guid = createGuid();
>guid : string & { $Guid: any; }
>createGuid() : string & { $Guid: any; }
>createGuid : () => string & { $Guid: any; }
map1[guid] = 123; // Can with tagged string
>map1[guid] = 123 : number
>map1[guid] : number
>map1 : { [x: string]: number; }
>guid : string & { $Guid: any; }
>123 : number
let map2: { [x: number]: string } = {};
>map2 : { [x: number]: string; }
>x : number
>{} : { [x: number]: undefined; }
let serialNo = createSerialNo();
>serialNo : number & { $SerialNo: any; }
>createSerialNo() : number & { $SerialNo: any; }
>createSerialNo : () => number & { $SerialNo: any; }
map2[serialNo] = "hello"; // Can index with tagged number
>map2[serialNo] = "hello" : string
>map2[serialNo] : string
>map2 : { [x: number]: string; }
>serialNo : number & { $SerialNo: any; }
>"hello" : string
const s1 = "{" + guid + "}";
>s1 : string
>"{" + guid + "}" : string
>"{" + guid : string
>"{" : string
>guid : string & { $Guid: any; }
>"}" : string
const s2 = guid.toLowerCase();
>s2 : string
>guid.toLowerCase() : string
>guid.toLowerCase : () => string
>guid : string & { $Guid: any; }
>toLowerCase : () => string
const s3 = guid + guid;
>s3 : string
>guid + guid : string
>guid : string & { $Guid: any; }
>guid : string & { $Guid: any; }
const s4 = guid + serialNo;
>s4 : string
>guid + serialNo : string
>guid : string & { $Guid: any; }
>serialNo : number & { $SerialNo: any; }
const s5 = serialNo.toPrecision(0);
>s5 : string
>serialNo.toPrecision(0) : string
>serialNo.toPrecision : (precision?: number) => string
>serialNo : number & { $SerialNo: any; }
>toPrecision : (precision?: number) => string
>0 : number
const n1 = serialNo * 3;
>n1 : number
>serialNo * 3 : number
>serialNo : number & { $SerialNo: any; }
>3 : number
const n2 = serialNo + serialNo;
>n2 : number
>serialNo + serialNo : number
>serialNo : number & { $SerialNo: any; }
>serialNo : number & { $SerialNo: any; }
const b1 = guid === "";
>b1 : boolean
>guid === "" : boolean
>guid : string & { $Guid: any; }
>"" : string
const b2 = guid === guid;
>b2 : boolean
>guid === guid : boolean
>guid : string & { $Guid: any; }
>guid : string & { $Guid: any; }
const b3 = serialNo === 0;
>b3 : boolean
>serialNo === 0 : boolean
>serialNo : number & { $SerialNo: any; }
>0 : number
const b4 = serialNo === serialNo;
>b4 : boolean
>serialNo === serialNo : boolean
>serialNo : number & { $SerialNo: any; }
>serialNo : number & { $SerialNo: any; }
@@ -0,0 +1,30 @@
type Guid = string & { $Guid }; // Tagged string type
type SerialNo = number & { $SerialNo }; // Tagged number type
function createGuid() {
return "21EC2020-3AEA-4069-A2DD-08002B30309D" as Guid;
}
function createSerialNo() {
return 12345 as SerialNo;
}
let map1: { [x: string]: number } = {};
let guid = createGuid();
map1[guid] = 123; // Can with tagged string
let map2: { [x: number]: string } = {};
let serialNo = createSerialNo();
map2[serialNo] = "hello"; // Can index with tagged number
const s1 = "{" + guid + "}";
const s2 = guid.toLowerCase();
const s3 = guid + guid;
const s4 = guid + serialNo;
const s5 = serialNo.toPrecision(0);
const n1 = serialNo * 3;
const n2 = serialNo + serialNo;
const b1 = guid === "";
const b2 = guid === guid;
const b3 = serialNo === 0;
const b4 = serialNo === serialNo;