Skip to content

Allow operators and indexing with intersections involving primtive types #6947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 41 additions & 33 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)) {
Expand All @@ -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));
Expand Down Expand Up @@ -13704,7 +13712,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) {
Expand All @@ -13728,7 +13736,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) {
Expand Down Expand Up @@ -15934,22 +15942,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)) {
Expand Down
57 changes: 57 additions & 0 deletions tests/baselines/reference/operatorsAndIntersectionTypes.js
Original file line number Diff line number Diff line change
@@ -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;
100 changes: 100 additions & 0 deletions tests/baselines/reference/operatorsAndIntersectionTypes.symbols
Original file line number Diff line number Diff line change
@@ -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))

Loading