8000 Add type relationship functions to checker api · microsoft/TypeScript@806ffe1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 806ffe1

Browse files
committed
Add type relationship functions to checker api
1 parent 3f1ec7a commit 806ffe1

File tree

5 files changed

+336
-1
lines changed

5 files changed

+336
-1
lines changed

Jakefile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ var harnessSources = harnessCoreSources.concat([
152152
"transpile.ts",
153153
"reuseProgramStructure.ts",
154154
"cachingInServerLSHost.ts",
155+
"checkerPublicRelationships.ts",
155156
"moduleResolution.ts",
156157
"tsconfigParsing.ts",
157158
"commandLineParsing.ts",

src/compiler/checker.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,59 @@ namespace ts {
103103

104104
getJsxElementAttributesType,
105105
getJsxIntrinsicTagNames,
106-
isOptionalParameter
106+
isOptionalParameter,
107+
108+
isIdenticalTo: (a, b) => checkTypeRelatedTo(a, b, identityRelation, /*errorNode*/undefined),
109+
isSubtypeOf: (a, b) => checkTypeRelatedTo(a, b, subtypeRelation, /*errorNode*/undefined),
110+
isAssignableTo: (a, b) => checkTypeRelatedTo(a, b, assignableRelation, /*errorNode*/undefined),
111+
isComparableTo: areTypesComparable,
112+
isInstantiationOf: (a, b) => {
113+
return a && b && (a.target === b);
114+
},
115+
116+
lookupGlobalType: name => {
117+
const symbol = getSymbol(globals, name, SymbolFlags.Type);
118+
return symbol ? getDeclaredTypeOfSymbol(symbol) : unknownType;
119+
},
120+
lookupGlobalValueType: name => {
121+
const symbol = getSymbol(globals, name, SymbolFlags.Value);
122+
return symbol ? getTypeOfSymbol(symbol) : unknownType;
123+
},
124+
lookupTypeAt: (name, node) => {
125+
const symbol = resolveName(node, name, SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined);
126+
return symbol ? getDeclaredTypeOfSymbol(symbol) : unknownType;
127+
},
128+
lookupValueTypeAt: (name, node) => {
129+
const symbol = resolveName(node, name, SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined);
130+
return symbol ? getTypeOfSymbol(symbol) : unknownType;
131+
},
132+
getTypeOfSymbol,
133+
134+
getAnyType: () => anyType,
135+
getStringType: () => stringType,
136+
getNumberType: () => numberType,
137+
getBooleanType: () => booleanType,
138+
getVoidType: () => voidType,
139+
getUndefinedType: () => undefinedType,
140+
getNullType: () => nullType,
141+
getESSymbolType: () => esSymbolType,
142+
getNeverType: () => neverType,
143+
getUnknownType: () => unknownType,
144+
getStringLiteralType: (text: string) => {
145+
/* tslint:disable:no-null-keyword */
146+
Debug.assert(text !== undefined && text !== null);
147+
/* tslint:enable:no-null-keyword */
148+
return getLiteralTypeForText(TypeFlags.StringLiteral, "" + text);
149+
},
150+
getNumberLiteralType: (text: string) => {
151+
/* tslint:disable:no-null-keyword */
152+
Debug.assert(text !== undefined && text !== null);
153+
/* tslint:enable:no-null-keyword */
154+
Debug.assert(typeof text === "string" || typeof text === "number"); // While not formally part of the function signature, allow coercions from numbers
155+
return getLiteralTypeForText(TypeFlags.NumberLiteral, "" + text);
156+
},
157+
getFalseType: () => falseType,
158+
getTrueType: () => trueType,
107159
};
108160

109161
const tupleTypes: Map<TupleType> = {};

src/compiler/types.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,6 +1872,100 @@ namespace ts {
18721872
getJsxIntrinsicTagNames(): Symbol[];
18731873
isOptionalParameter(node: ParameterDeclaration): boolean;
18741874

1875+
/**
1876+
* Two types are considered identical when
1877+
* - they are both the `any` type,
1878+
* - they are the same primitive type,
1879+
* - they are the same type parameter,
1880+
* - they are union types with identical sets of constituent types, or
1881+
* - they are intersection types with identical sets of constituent types, or
1882+
* - they are object types with identical sets of members.
1883+
*
1884+
* This relationship is bidirectional.
1885+
* See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.2) for more information.
1886+
*/
1887+
isIdenticalTo(a: Type, b: Type): boolean;
1888+
/**
1889+
* `a` is a ___subtype___ of `b` (and `b` is a ___supertype___ of `a`) if `a` has no excess properties with respect to `b`,
1890+
* and one of the following is true:
1891+
* - `a` and `b` are identical types.
1892+
* - `b` is the `any` type.
1893+
* - `a` is the `undefined` type.
1894+
* - `a` is the `null` type and `b` is _not_ the `undefined` type.
1895+
* - `a` is an enum type and `b` is the primitive type `number`.
1896+
* - `a` is a string literal type and `b` is the primitive type `string`.
1897+
* - `a` is a union type and each constituient type of `b` is a subtype of `b`.
1898+
* - `a` is an intersection type and at least one constituent type of `a` is a subtype of `b`.
1899+
* - `b` is a union type and `a` is a subtype of at least one constituent type of `b`.
1900+
* - `b` is an intersection type and `a` is a subtype of each constituent type of `b`.
1901+
* - `a` is a type parameter and the constraint of `a` is a subtype of `b`.
1902+
* - `a` has a subset of the structural members of `b`.
1903+
*
1904+
* This relationship is directional.
1905+
* See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.3) for more information.
1906+
*/
1907+
isSubtypeOf(a: Type, b: Type): boolean;
1908+
/**
1909+
* The assignable relationship differs only from the subtype relationship in that:
1910+
* - the `any` type is assignable to, but not a subtype of, all types
1911+
* - the primitive type `number` is assignable to, but not a subtype of, all enum types, and
1912+
* - an object type without a particular property is assignable to an object type in which that property is optional.
1913+
*
1914+
* This relationship is directional.
1915+
* See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.4) for more information.
1916+
*/
1917+
isAssignableTo(a: Type, b: Type): boolean;
1918+
/**
1919+
* True if `a` is assignable to `b`, or `b` is assignable to `a`. Additionally, all unions with
1920+
* overlapping constituient types are comparable, and unit types in the same domain are comparable.
1921+
* This relationship is bidirectional.
1922+
*/
1923+
isComparableTo(a: Type, b: Type): boolean;
1924+
/**
1925+
* Not a formal relationship - returns true if a is an instantiation of the generic type b
1926+
*/
1927+
isInstantiationOf(a: GenericType, b: GenericType): boolean;
1928+
1929+
/**
1930+
* Returns the declared type of the globally named symbol with meaning SymbolFlags.Type
1931+
* Returns the unknown type on failure.
1932+
*/
1933+
lookupGlobalType(name: string): Type;
1934+
/**
1935+
* Returns the declared type of the globally named symbol with meaning SymbolFlags.Type
1936+
* Returns the unknown type on failure.
1937+
*/
1938+
lookupGlobalValueType(name: string): Type;
1939+
/**
1940+
* Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Type
1941+
* Returns the unknown type on failure.
1942+
*/
1943+
lookupTypeAt(name: string, position: Node): Type;
1944+
/**
1945+
* Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Type
1946+
* Returns the unknown type on failure.
1947+
*/
1948+
lookupValueTypeAt(name: string, position: Node): Type;
1949+
/**
1950+
* Returns the type of a symbol
1951+
*/
1952+
getTypeOfSymbol(symbol: Symbol): Type;
1953+
1954+
getAnyType(): Type;
1955+
getStringType(): Type;
1956+
getNumberType(): Type;
1957+
getBooleanType(): Type;
1958+
getVoidType(): Type;
1959+
getUndefinedType(): Type;
1960+
getNullType(): Type;
1961+
getESSymbolType(): Type;
1962+
getNeverType(): Type;
1963+
getUnknownType(): Type;
1964+
getStringLiteralType(text: string): LiteralType;
1965+
getNumberLiteralType(text: string): LiteralType;
1966+
getFalseType(): Type;
1967+
getTrueType(): Type;
1968+
18751969
// Should not be called directly. Should only be accessed through the Program instance.
18761970
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
18771971
/* @internal */ getGlobalDiagnostics(): Diagnostic[];

src/harness/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"./unittests/transpile.ts",
8484
"./unittests/reuseProgramStructure.ts",
8585
"./unittests/cachingInServerLSHost.ts",
86+
"./unittests/checkerPublicRelationships.ts",
8687
"./unittests/moduleResolution.ts",
8788
"./unittests/tsconfigParsing.ts",
8889
"./unittests/commandLineParsing.ts",
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/// <reference path="..\harness.ts" />
2+
/// <reference path="..\..\harness\harnessLanguageService.ts" />
3+
4+
namespace ts {
5+
describe("Type Checker Public Relationship APIs", () => {
6+
let checker: TypeChecker;
7+
let host: CompilerHost;
8+
let program: Program;
9+
before(() => {
10+
host = Harness.Compiler.createCompilerHost([{
11+
unitName: "test.ts",
12+
content: `
13+
type FunctionAlias = Function;
14+
function foo() {
15+
type Function = { myBrand: 42 } & FunctionAlias;
16+
return (() => {}) as any as Function;
17+
}
18+
function foo2<T>(x: T) {
19+
type Function = { myBrand: T } & FunctionAlias;
20+
const ret = (() => {}) as any as Function;
21+
ret.myBrand = x;
22+
return ret;
23+
}`
24+
}], () => void 0, ScriptTarget.ES3, /*useCaseSensitiveFileNames*/true, "", NewLineKind.CarriageReturnLineFeed);
25+
program = ts.createProgram(["test.ts"], ts.defaultInitCompilerOptions, host);
26+
const diag = ts.getPreEmitDiagnostics(program);
27+
if (diag.length) {
28+
const errors = ts.formatDiagnostics(diag, host);
29+
console.log(errors);
30+
}
31+
checker = program.getTypeChecker();
32+
});
33+
34+
it("can get the any type", () => {
35+
assert(checker.getAnyType().flags & TypeFlags.Any);
36+
});
37+
38+
it("can get the string type", () => {
39+
assert(checker.getStringType().flags & TypeFlags.String);
40+
});
41+
42+
it("can get the number type", () => {
43+
assert(checker.getNumberType().flags & TypeFlags.Number);
44+
});
45+
46+
it("can get the boolean type", () => {
47+
assert(checker.getBooleanType().flags & TypeFlags.Boolean);
48+
});
49+
50+
it("can get the void type", () => {
51+
assert(checker.getVoidType().flags & TypeFlags.Void);
52+
});
53+
54+
it("can get the undefined type", () => {
55+
assert(checker.getUndefinedType().flags & TypeFlags.Undefined);
56+
});
57+
58+
it("can get the null type", () => {
59+
assert(checker.getNullType().flags & TypeFlags.Null);
60+
});
61+
62+
it("can get the essymbol type", () => {
63+
assert(checker.getESSymbolType().flags & TypeFlags.ESSymbol);
64+
});
65+
66+
it("can get the never type", () => {
67+
assert(checker.getNeverType().flags & TypeFlags.Never);
68+
});
69+
70+
it("can get the unknown type", () => {
71+
assert(checker.getUnknownType().flags & TypeFlags.Any);
72+
assert(checker.getUnknownType() === checker.getUnknownType());
73+
});
74+
75+
it("can get the true type", () => {
76+
assert(checker.getTrueType().flags & TypeFlags.BooleanLiteral);
77+
});
78+
79+
it("can get the false type", () => {
80+
assert(checker.getFalseType().flags & TypeFlags.BooleanLiteral);
81+
});
82+
83+
it("ensures true and false are different types", () => {
84+
assert(checker.getFalseType() !== checker.getTrueType());
85+
});
86+
87+
it("can get string literal types", () => {
88+
assert((checker.getStringLiteralType("foobar") as LiteralType).text === "foobar");
89+
});
90+
91+
it("can get numeber literal types", () => {
92+
assert((checker.getNumberLiteralType("42") as LiteralType).text === "42");
93+
});
94+
95+
it("doesn't choke on exceptional input to literal type getters", () => {
96+
assert.equal((checker.getStringLiteralType("") as LiteralType).text, "");
97+
assert.throws(() => checker.getStringLiteralType(undefined), Error, "Debug Failure. False expression:");
98+
/* tslint:disable:no-null-keyword */
99+
assert.throws(() => checker.getStringLiteralType(null), Error, "Debug Failure. False expression:");
100+
/* tslint:enable:no-null-keyword */
101+
let hugeStringLiteral = map(new Array(2 ** 16 - 1), () => "a").join();
102+
assert.equal((checker.getStringLiteralType(hugeStringLiteral) as LiteralType).text, hugeStringLiteral);
103+
hugeStringLiteral = undefined;
104+
105+
106+
assert.throws(() => checker.getNumberLiteralType(undefined), Error, "Debug Failure. False expression:");
107+
/* tslint:disable:no-null-keyword */
108+
assert.throws(() => checker.getNumberLiteralType(null), Error, "Debug Failure. False expression:");
109+
/* tslint:enable:no-null-keyword */
110+
111+
const sanityChecks = ["000", "0b0", "0x0", "0.0", "0e-0", "-010", "-0b10", "-0x10", "-0o10", "-10.0", "-1e-1", "NaN", "Infinity", "-Infinity"];
112+
forEach(sanityChecks, num => {
113+
assert.equal((checker.getNumberLiteralType(num) as LiteralType).text, num, `${num} did not match.`);
114+
});
115+
116+
const insanityChecks = [[0, "0"], [0b0, "0"], [-10, "-10"], [NaN, "NaN"], [Infinity, "Infinity"], [-Infinity, "-Infinity"]];
117+
forEach(insanityChecks, ([num, expected]) => {
118+
assert.equal((checker.getNumberLiteralType(num as any) as LiteralType).text, expected, `${JSON.stringify(num)} should be ${expected}`);
119+
});
120+
121+
const instabilityChecks = [{ foo: 42 }, new Date(42), [42], new Number(42), new String("42")];
122+
forEach(instabilityChecks, (bad) => {
123+
assert.throws(() => checker.getNumberLiteralType(bad as any));
124+
});
125+
});
126+
127+
it("can look up global types", () => {
128+
assert.equal(checker.lookupGlobalType("Array").symbol.name, "Array", "Array global symbol not named Array");
129+
const globalFunction = checker.lookupGlobalType("Function");
130+
const globalAlias = checker.lookupGlobalType("FunctionAlias");
131+
assert.notEqual(globalFunction, checker.getUnknownType(), "The global function type should not be the unknown type");
132+
assert.notEqual(globalAlias, checker.getUnknownType(), "The global alias function type should not be the unknown type");
133+
const globalFunctionLength = globalFunction.getProperty("length");
134+
const aliasFunctionLength = globalAlias.getProperty("length");
135+
assert(globalFunctionLength, "Global function length symbol should exist");
136+
assert(aliasFunctionLength, "Alias function length symbol should exist");
137+
assert.notEqual(checker.getTypeOfSymbol(globalFunctionLength), checker.getUnknownType(), "The global function's length type should not be unknown");
138+
assert.notEqual(checker.getTypeOfSymbol(aliasFunctionLength), checker.getUnknownType(), "The alias function's length type should not be unknown");
139+
assert.equal(checker.getTypeOfSymbol(globalFunctionLength), checker.getTypeOfSymbol(aliasFunctionLength), "Alias and global function length were not identical types");
140+
assert.equal((checker.getTypeOfSymbol(globalFunctionLength) as IntrinsicType).intrinsicName, (checker.getNumberType() as IntrinsicType).intrinsicName, "Function length was not number type");
141+
});
142+
143+
it("can look up types in a given scope", () => {
144+
assert(program.getSourceFile("test.ts"), "Test file not found");
145+
const functionBody = forEachChild(program.getSourceFile("test.ts"), node => node.kind === SyntaxKind.FunctionDeclaration ? (node as FunctionDeclaration) : undefined).body;
146+
assert(functionBody, "Function body missing");
147+
const innerFunction = checker.lookupTypeAt("Function", functionBody.statements[functionBody.statements.length - 1]);
148+
assert(innerFunction, "Inner function type missing");
149+
assert.notEqual(innerFunction, checker.getUnknownType(), "Inner function type should not be unknown");
150+
assert.notEqual(checker.lookupGlobalType("Function"), innerFunction, "Inner function type should be different than global");
151+
const brandNameType = checker.getTypeOfSymbol(innerFunction.getProperty("myBrand"));
152+
assert.notEqual(brandNameType, checker.getUnknownType(), "Brand type on inner function should not be unknown");
153+
assert.equal(brandNameType, checker.getNumberLiteralType("42"), "Brand type should be 42");
154+
155+
let skipped = false;
156+
const functionBody2 = forEachChild(program.getSourceFile("test.ts"), node => node.kind === SyntaxKind.FunctionDeclaration ? skipped ? (node as FunctionDeclaration) : (skipped = true, undefined) : undefined).body;
157+
assert(functionBody2, "Function body missing");
158+
const innerFunction2 = checker.lookupTypeAt("Function", functionBody2.statements[functionBody2.statements.length - 1]);
159+
assert(innerFunction2, "Inner function type missing");
160+
assert.notEqual(innerFunction2, checker.getUnknownType(), "Inner function type should not be unknown");
161+
assert.notEqual(checker.lookupGlobalType("Function"), innerFunction2, "Inner function type should be different than global");
162+
const brandNameType2 = checker.getTypeOfSymbol(innerFunction2.getProperty("myBrand"));
163+
assert.notEqual(brandNameType2, checker.getUnknownType(), "Brand type on inner function should not be unknown");
164+
const functionType = checker.lookupGlobalValueType("foo2");
165+
assert.notEqual(functionType, checker.getUnknownType(), "foo2 function type should not be unknown");
166+
assert(brandNameType2.flags & TypeFlags.TypeParameter, "Brand should be a type parameter");
167+
assert.equal(brandNameType2, checker.lookupGlobalValueType("foo2").getCallSignatures()[0].getTypeParameters()[0], "Brand type should be a type parameter");
168+
});
169+
170+
it("can compare types using all the builtin relationships", () => {
171+
assert(checker.isSubtypeOf(checker.getNumberType(), checker.getAnyType()), "Any should be a subtype of number");
172+
assert.isFalse(checker.isSubtypeOf(checker.getAnyType(), checker.getNumberType()), "Number should not be a subtype of any");
173+
174+
assert(checker.isAssignableTo(checker.getAnyType(), checker.getNumberType()), "Any should be assignable to number");
175+
assert(checker.isAssignableTo(checker.getFalseType(), checker.getBooleanType()), "False should be assignable to boolean");
176+
177+
assert(checker.isComparableTo(checker.getBooleanType(), checker.getFalseType()), "False and boolean are comparable");
178+
assert(checker.isComparableTo(checker.getFalseType(), checker.getBooleanType()), "Boolean and false are comparable");
179+
});
180+
181+
after(() => {
182+
checker = undefined;
183+
host = undefined;
184+
program = undefined;
185+
});
186+
});
187+
}

0 commit comments

Comments
 (0)
0