From dc8cf0d281ff4e036c2c99893cbfbb040d43f51f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 25 Jul 2016 18:57:37 -0700 Subject: [PATCH 1/5] Add type relationship functions to checker api --- Jakefile.js | 1 + src/compiler/checker.ts | 53 +++++ src/compiler/types.ts | 95 +++++++++ src/harness/tsconfig.json | 1 + .../unittests/checkerPublicRelationships.ts | 195 ++++++++++++++++++ 5 files changed, 345 insertions(+) create mode 100644 src/harness/unittests/checkerPublicRelationships.ts diff --git a/Jakefile.js b/Jakefile.js index ad853238111e4..f5bbf1f17004e 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -123,6 +123,7 @@ var harnessSources = harnessCoreSources.concat([ "reuseProgramStructure.ts", "textStorage.ts", "cachingInServerLSHost.ts", + "checkerPublicRelationships.ts", "moduleResolution.ts", "tsconfigParsing.ts", "commandLineParsing.ts", diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c87062a212f8e..8334e40715cf2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -233,6 +233,59 @@ namespace ts { return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined); }, getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()), + + isIdenticalTo: (a, b) => checkTypeRelatedTo(a, b, identityRelation, /*errorNode*/ undefined), + isSubtypeOf: (a, b) => checkTypeRelatedTo(a, b, subtypeRelation, /*errorNode*/ undefined), + isAssignableTo: (a, b) => checkTypeRelatedTo(a, b, assignableRelation, /*errorNode*/ undefined), + isComparableTo: areTypesComparable, + isInstantiationOf: (a, b) => { + return a && b && (a.target === b); + }, + + lookupGlobalType: name => { + const symbol = getSymbol(globals, escapeLeadingUnderscores(name), SymbolFlags.Type); + return symbol ? getDeclaredTypeOfSymbol(symbol) : unknownType; + }, + lookupGlobalValueType: name => { + const symbol = getSymbol(globals, escapeLeadingUnderscores(name), SymbolFlags.Value); + return symbol ? getTypeOfSymbol(symbol) : unknownType; + }, + lookupTypeAt: (name, node) => { + const symbol = resolveName(node, escapeLeadingUnderscores(name), SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined); + return symbol ? getDeclaredTypeOfSymbol(symbol) : unknownType; + }, + lookupValueTypeAt: (name, node) => { + const symbol = resolveName(node, escapeLeadingUnderscores(name), SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined); + return symbol ? getTypeOfSymbol(symbol) : unknownType; + }, + getTypeOfSymbol, + + getAnyType: () => anyType, + getStringType: () => stringType, + getNumberType: () => numberType, + getBooleanType: () => booleanType, + getVoidType: () => voidType, + getUndefinedType: () => undefinedType, + getNullType: () => nullType, + getESSymbolType: () => esSymbolType, + getNeverType: () => neverType, + getUnknownType: () => unknownType, + getStringLiteralType: (text: string) => { + /* tslint:disable:no-null-keyword */ + Debug.assert(text !== undefined && text !== null); + /* tslint:enable:no-null-keyword */ + Debug.assert(typeof text === "string"); + return getLiteralType(text); + }, + getNumberLiteralType: (number: number) => { + /* tslint:disable:no-null-keyword */ + Debug.assert(number !== undefined && number !== null); + /* tslint:enable:no-null-keyword */ + Debug.assert(typeof number === "number"); + return getLiteralType(number); + }, + getFalseType: () => falseType, + getTrueType: () => trueType, }; const tupleTypes: GenericType[] = []; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cfb08a7b05abd..7d02d434ad460 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2686,8 +2686,103 @@ namespace ts { /* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined; + /* @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker; + /** + * Two types are considered identical when + * - they are both the `any` type, + * - they are the same primitive type, + * - they are the same type parameter, + * - they are union types with identical sets of constituent types, or + * - they are intersection types with identical sets of constituent types, or + * - they are object types with identical sets of members. + * + * This relationship is bidirectional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.2) for more information. + */ + isIdenticalTo(a: Type, b: Type): boolean; + /** + * `a` is a ___subtype___ of `b` (and `b` is a ___supertype___ of `a`) if `a` has no excess properties with respect to `b`, + * and one of the following is true: + * - `a` and `b` are identical types. + * - `b` is the `any` type. + * - `a` is the `undefined` type. + * - `a` is the `null` type and `b` is _not_ the `undefined` type. + * - `a` is an enum type and `b` is the primitive type `number`. + * - `a` is a string literal type and `b` is the primitive type `string`. + * - `a` is a union type and each constituient type of `b` is a subtype of `b`. + * - `a` is an intersection type and at least one constituent type of `a` is a subtype of `b`. + * - `b` is a union type and `a` is a subtype of at least one constituent type of `b`. + * - `b` is an intersection type and `a` is a subtype of each constituent type of `b`. + * - `a` is a type parameter and the constraint of `a` is a subtype of `b`. + * - `a` has a subset of the structural members of `b`. + * + * This relationship is directional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.3) for more information. + */ + isSubtypeOf(a: Type, b: Type): boolean; + /** + * The assignable relationship differs only from the subtype relationship in that: + * - the `any` type is assignable to, but not a subtype of, all types + * - the primitive type `number` is assignable to, but not a subtype of, all enum types, and + * - an object type without a particular property is assignable to an object type in which that property is optional. + * + * This relationship is directional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.4) for more information. + */ + isAssignableTo(a: Type, b: Type): boolean; + /** + * True if `a` is assignable to `b`, or `b` is assignable to `a`. Additionally, all unions with + * overlapping constituient types are comparable, and unit types in the same domain are comparable. + * This relationship is bidirectional. + */ + isComparableTo(a: Type, b: Type): boolean; + /** + * Not a formal relationship - returns true if a is an instantiation of the generic type b + */ + isInstantiationOf(a: GenericType, b: GenericType): boolean; + + /** + * Returns the declared type of the globally named symbol with meaning SymbolFlags.Type + * Returns the unknown type on failure. + */ + lookupGlobalType(name: string): Type; + /** + * Returns the declared type of the globally named symbol with meaning SymbolFlags.Value + * Returns the unknown type on failure. + */ + lookupGlobalValueType(name: string): Type; + /** + * Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Type + * Returns the unknown type on failure. + */ + lookupTypeAt(name: string, position: Node): Type; + /** + * Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Value + * Returns the unknown type on failure. + */ + lookupValueTypeAt(name: string, position: Node): Type; + /** + * Returns the type of a symbol + */ + getTypeOfSymbol(symbol: Symbol): Type; + + getAnyType(): Type; + getStringType(): Type; + getNumberType(): Type; + getBooleanType(): Type; + getVoidType(): Type; + getUndefinedType(): Type; + getNullType(): Type; + getESSymbolType(): Type; + getNeverType(): Type; + getUnknownType(): Type; + getStringLiteralType(text: string): LiteralType; + getNumberLiteralType(num: number): LiteralType; + getFalseType(): Type; + getTrueType(): Type; + // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /* @internal */ getGlobalDiagnostics(): Diagnostic[]; diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index bd7c9bc2ffab4..a77b9b9f31161 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -110,6 +110,7 @@ "./unittests/transpile.ts", "./unittests/reuseProgramStructure.ts", "./unittests/cachingInServerLSHost.ts", + "./unittests/checkerPublicRelationships.ts", "./unittests/moduleResolution.ts", "./unittests/tsconfigParsing.ts", "./unittests/commandLineParsing.ts", diff --git a/src/harness/unittests/checkerPublicRelationships.ts b/src/harness/unittests/checkerPublicRelationships.ts new file mode 100644 index 0000000000000..c4b1ef895c6a2 --- /dev/null +++ b/src/harness/unittests/checkerPublicRelationships.ts @@ -0,0 +1,195 @@ +/// +/// + +namespace ts { + describe("Type Checker Public Relationship APIs", () => { + let checker: TypeChecker; + let host: CompilerHost; + let program: Program; + before(() => { + host = Harness.Compiler.createCompilerHost([{ + unitName: "test.ts", + content: ` + type FunctionAlias = Function; + function foo() { + type Function = { myBrand: 42 } & FunctionAlias; + return (() => {}) as any as Function; + } + function foo2(x: T) { + type Function = { myBrand: T } & FunctionAlias; + const ret = (() => {}) as any as Function; + ret.myBrand = x; + return ret; + } + const xs: number[] = [1,2,3]; + ` + }], () => void 0, ScriptTarget.ES3, /*useCaseSensitiveFileNames*/true, "", NewLineKind.CarriageReturnLineFeed); + program = ts.createProgram(["test.ts"], ts.defaultInitCompilerOptions, host); + const diag = ts.getPreEmitDiagnostics(program); + if (diag.length) { + const errors = ts.formatDiagnostics(diag, host); + console.log(errors); + } + checker = program.getTypeChecker(); + }); + + it("can get the any type", () => { + assert(checker.getAnyType().flags & TypeFlags.Any); + }); + + it("can get the string type", () => { + assert(checker.getStringType().flags & TypeFlags.String); + }); + + it("can get the number type", () => { + assert(checker.getNumberType().flags & TypeFlags.Number); + }); + + it("can get the boolean type", () => { + assert(checker.getBooleanType().flags & TypeFlags.Boolean); + }); + + it("can get the void type", () => { + assert(checker.getVoidType().flags & TypeFlags.Void); + }); + + it("can get the undefined type", () => { + assert(checker.getUndefinedType().flags & TypeFlags.Undefined); + }); + + it("can get the null type", () => { + assert(checker.getNullType().flags & TypeFlags.Null); + }); + + it("can get the essymbol type", () => { + assert(checker.getESSymbolType().flags & TypeFlags.ESSymbol); + }); + + it("can get the never type", () => { + assert(checker.getNeverType().flags & TypeFlags.Never); + }); + + it("can get the unknown type", () => { + assert(checker.getUnknownType().flags & TypeFlags.Any); + assert(checker.getUnknownType() === checker.getUnknownType()); + }); + + it("can get the true type", () => { + assert(checker.getTrueType().flags & TypeFlags.BooleanLiteral); + }); + + it("can get the false type", () => { + assert(checker.getFalseType().flags & TypeFlags.BooleanLiteral); + }); + + it("ensures true and false are different types", () => { + assert(checker.getFalseType() !== checker.getTrueType()); + }); + + it("can get string literal types", () => { + assert((checker.getStringLiteralType("foobar") as LiteralType).text === "foobar"); + }); + + it("can get numeber literal types", () => { + assert((checker.getNumberLiteralType("42") as LiteralType).text === "42"); + }); + + it("doesn't choke on exceptional input to literal type getters", () => { + assert.equal((checker.getStringLiteralType("") as LiteralType).text, ""); + assert.throws(() => checker.getStringLiteralType(undefined), Error, "Debug Failure. False expression:"); + /* tslint:disable:no-null-keyword */ + assert.throws(() => checker.getStringLiteralType(null), Error, "Debug Failure. False expression:"); + /* tslint:enable:no-null-keyword */ + let hugeStringLiteral = map(new Array(2 ** 16 - 1), () => "a").join(); + assert.equal((checker.getStringLiteralType(hugeStringLiteral) as LiteralType).text, hugeStringLiteral); + hugeStringLiteral = undefined; + + + assert.throws(() => checker.getNumberLiteralType(undefined), Error, "Debug Failure. False expression:"); + /* tslint:disable:no-null-keyword */ + assert.throws(() => checker.getNumberLiteralType(null), Error, "Debug Failure. False expression:"); + /* tslint:enable:no-null-keyword */ + + const sanityChecks = ["000", "0b0", "0x0", "0.0", "0e-0", "-010", "-0b10", "-0x10", "-0o10", "-10.0", "-1e-1", "NaN", "Infinity", "-Infinity"]; + forEach(sanityChecks, num => { + assert.equal((checker.getNumberLiteralType(num) as LiteralType).text, num, `${num} did not match.`); + }); + + const insanityChecks = [[0, "0"], [0b0, "0"], [-10, "-10"], [NaN, "NaN"], [Infinity, "Infinity"], [-Infinity, "-Infinity"]]; + forEach(insanityChecks, ([num, expected]) => { + assert.equal((checker.getNumberLiteralType(num as any) as LiteralType).text, expected, `${JSON.stringify(num)} should be ${expected}`); + }); + + const instabilityChecks = [{ foo: 42 }, new Date(42), [42], new Number(42), new String("42")]; + forEach(instabilityChecks, (bad) => { + assert.throws(() => checker.getNumberLiteralType(bad as any)); + }); + }); + + it("can look up global types", () => { + assert.equal(checker.lookupGlobalType("Array").symbol.name, "Array", "Array global symbol not named Array"); + const globalFunction = checker.lookupGlobalType("Function"); + const globalAlias = checker.lookupGlobalType("FunctionAlias"); + assert.notEqual(globalFunction, checker.getUnknownType(), "The global function type should not be the unknown type"); + assert.notEqual(globalAlias, checker.getUnknownType(), "The global alias function type should not be the unknown type"); + const globalFunctionLength = globalFunction.getProperty("length"); + const aliasFunctionLength = globalAlias.getProperty("length"); + assert(globalFunctionLength, "Global function length symbol should exist"); + assert(aliasFunctionLength, "Alias function length symbol should exist"); + assert.notEqual(checker.getTypeOfSymbol(globalFunctionLength), checker.getUnknownType(), "The global function's length type should not be unknown"); + assert.notEqual(checker.getTypeOfSymbol(aliasFunctionLength), checker.getUnknownType(), "The alias function's length type should not be unknown"); + assert.equal(checker.getTypeOfSymbol(globalFunctionLength), checker.getTypeOfSymbol(aliasFunctionLength), "Alias and global function length were not identical types"); + assert.equal((checker.getTypeOfSymbol(globalFunctionLength) as IntrinsicType).intrinsicName, (checker.getNumberType() as IntrinsicType).intrinsicName, "Function length was not number type"); + }); + + it("can look up types in a given scope", () => { + assert(program.getSourceFile("test.ts"), "Test file not found"); + const functionBody = forEachChild(program.getSourceFile("test.ts"), node => node.kind === SyntaxKind.FunctionDeclaration ? (node as FunctionDeclaration) : undefined).body; + assert(functionBody, "Function body missing"); + const innerFunction = checker.lookupTypeAt("Function", functionBody.statements[functionBody.statements.length - 1]); + assert(innerFunction, "Inner function type missing"); + assert.notEqual(innerFunction, checker.getUnknownType(), "Inner function type should not be unknown"); + assert.notEqual(checker.lookupGlobalType("Function"), innerFunction, "Inner function type should be different than global"); + const brandNameType = checker.getTypeOfSymbol(innerFunction.getProperty("myBrand")); + assert.notEqual(brandNameType, checker.getUnknownType(), "Brand type on inner function should not be unknown"); + assert.equal(brandNameType, checker.getNumberLiteralType("42"), "Brand type should be 42"); + + let skipped = false; + const functionBody2 = forEachChild(program.getSourceFile("test.ts"), node => node.kind === SyntaxKind.FunctionDeclaration ? skipped ? (node as FunctionDeclaration) : (skipped = true, undefined) : undefined).body; + assert(functionBody2, "Function body missing"); + const innerFunction2 = checker.lookupTypeAt("Function", functionBody2.statements[functionBody2.statements.length - 1]); + assert(innerFunction2, "Inner function type missing"); + assert.notEqual(innerFunction2, checker.getUnknownType(), "Inner function type should not be unknown"); + assert.notEqual(checker.lookupGlobalType("Function"), innerFunction2, "Inner function type should be different than global"); + const brandNameType2 = checker.getTypeOfSymbol(innerFunction2.getProperty("myBrand")); + assert.notEqual(brandNameType2, checker.getUnknownType(), "Brand type on inner function should not be unknown"); + const functionType = checker.lookupGlobalValueType("foo2"); + assert.notEqual(functionType, checker.getUnknownType(), "foo2 function type should not be unknown"); + assert(brandNameType2.flags & TypeFlags.TypeParameter, "Brand should be a type parameter"); + assert.equal(brandNameType2, checker.lookupGlobalValueType("foo2").getCallSignatures()[0].getTypeParameters()[0], "Brand type should be a type parameter"); + }); + + it("can compare types using all the builtin relationships", () => { + assert(checker.isSubtypeOf(checker.getNumberType(), checker.getAnyType()), "Any should be a subtype of number"); + assert.isFalse(checker.isSubtypeOf(checker.getAnyType(), checker.getNumberType()), "Number should not be a subtype of any"); + + assert(checker.isAssignableTo(checker.getAnyType(), checker.getNumberType()), "Any should be assignable to number"); + assert(checker.isAssignableTo(checker.getFalseType(), checker.getBooleanType()), "False should be assignable to boolean"); + + assert(checker.isComparableTo(checker.getBooleanType(), checker.getFalseType()), "False and boolean are comparable"); + assert(checker.isComparableTo(checker.getFalseType(), checker.getBooleanType()), "Boolean and false are comparable"); + + const variableType = checker.lookupGlobalValueType("xs"); + const globalArrayType = checker.lookupGlobalType("Array"); + assert.notEqual(variableType, checker.getUnknownType(), "xs type should not be unknown"); + assert.notEqual(globalArrayType, checker.getUnknownType(), "Global array type should not be unknown"); + assert(checker.isInstantiationOf(checker.lookupGlobalValueType("xs") as GenericType, checker.lookupGlobalType("Array") as GenericType)); + }); + + after(() => { + checker = undefined; + host = undefined; + program = undefined; + }); + }); +} \ No newline at end of file From ee18f7d8eacab2364f61734f7e29b0c8f34acdef Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 3 Jul 2017 14:40:37 -0700 Subject: [PATCH 2/5] Update for newer literal types --- .../unittests/checkerPublicRelationships.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/harness/unittests/checkerPublicRelationships.ts b/src/harness/unittests/checkerPublicRelationships.ts index c4b1ef895c6a2..176155d69ec68 100644 --- a/src/harness/unittests/checkerPublicRelationships.ts +++ b/src/harness/unittests/checkerPublicRelationships.ts @@ -23,7 +23,7 @@ namespace ts { } const xs: number[] = [1,2,3]; ` - }], () => void 0, ScriptTarget.ES3, /*useCaseSensitiveFileNames*/true, "", NewLineKind.CarriageReturnLineFeed); + }], () => void 0, ScriptTarget.ES3, /*useCaseSensitiveFileNames*/ true, "", NewLineKind.CarriageReturnLineFeed); program = ts.createProgram(["test.ts"], ts.defaultInitCompilerOptions, host); const diag = ts.getPreEmitDiagnostics(program); if (diag.length) { @@ -87,42 +87,42 @@ namespace ts { }); it("can get string literal types", () => { - assert((checker.getStringLiteralType("foobar") as LiteralType).text === "foobar"); + assert((checker.getLiteralType("foobar") as LiteralType).value === "foobar"); }); it("can get numeber literal types", () => { - assert((checker.getNumberLiteralType("42") as LiteralType).text === "42"); + assert((checker.getLiteralType(42) as LiteralType).value === "42"); }); it("doesn't choke on exceptional input to literal type getters", () => { - assert.equal((checker.getStringLiteralType("") as LiteralType).text, ""); - assert.throws(() => checker.getStringLiteralType(undefined), Error, "Debug Failure. False expression:"); + assert.equal((checker.getLiteralType("") as LiteralType).value, ""); + assert.throws(() => checker.getLiteralType(/*content*/ undefined), Error, "Debug Failure. False expression:"); /* tslint:disable:no-null-keyword */ - assert.throws(() => checker.getStringLiteralType(null), Error, "Debug Failure. False expression:"); + assert.throws(() => checker.getLiteralType(/*content*/ null), Error, "Debug Failure. False expression:"); /* tslint:enable:no-null-keyword */ let hugeStringLiteral = map(new Array(2 ** 16 - 1), () => "a").join(); - assert.equal((checker.getStringLiteralType(hugeStringLiteral) as LiteralType).text, hugeStringLiteral); + assert.equal((checker.getLiteralType(hugeStringLiteral) as LiteralType).value, hugeStringLiteral); hugeStringLiteral = undefined; - assert.throws(() => checker.getNumberLiteralType(undefined), Error, "Debug Failure. False expression:"); + assert.throws(() => checker.getLiteralType(/*content*/ undefined), Error, "Debug Failure. False expression:"); /* tslint:disable:no-null-keyword */ - assert.throws(() => checker.getNumberLiteralType(null), Error, "Debug Failure. False expression:"); + assert.throws(() => checker.getLiteralType(/*content*/ null), Error, "Debug Failure. False expression:"); /* tslint:enable:no-null-keyword */ const sanityChecks = ["000", "0b0", "0x0", "0.0", "0e-0", "-010", "-0b10", "-0x10", "-0o10", "-10.0", "-1e-1", "NaN", "Infinity", "-Infinity"]; forEach(sanityChecks, num => { - assert.equal((checker.getNumberLiteralType(num) as LiteralType).text, num, `${num} did not match.`); + assert.equal((checker.getLiteralType(num) as LiteralType).value, num, `${num} did not match.`); }); const insanityChecks = [[0, "0"], [0b0, "0"], [-10, "-10"], [NaN, "NaN"], [Infinity, "Infinity"], [-Infinity, "-Infinity"]]; forEach(insanityChecks, ([num, expected]) => { - assert.equal((checker.getNumberLiteralType(num as any) as LiteralType).text, expected, `${JSON.stringify(num)} should be ${expected}`); + assert.equal((checker.getLiteralType(num as any) as LiteralType).value, expected, `${JSON.stringify(num)} should be ${expected}`); }); const instabilityChecks = [{ foo: 42 }, new Date(42), [42], new Number(42), new String("42")]; forEach(instabilityChecks, (bad) => { - assert.throws(() => checker.getNumberLiteralType(bad as any)); + assert.throws(() => checker.getLiteralType(bad as any)); }); }); @@ -152,7 +152,7 @@ namespace ts { assert.notEqual(checker.lookupGlobalType("Function"), innerFunction, "Inner function type should be different than global"); const brandNameType = checker.getTypeOfSymbol(innerFunction.getProperty("myBrand")); assert.notEqual(brandNameType, checker.getUnknownType(), "Brand type on inner function should not be unknown"); - assert.equal(brandNameType, checker.getNumberLiteralType("42"), "Brand type should be 42"); + assert.equal(brandNameType, checker.getLiteralType(42), "Brand type should be 42"); let skipped = false; const functionBody2 = forEachChild(program.getSourceFile("test.ts"), node => node.kind === SyntaxKind.FunctionDeclaration ? skipped ? (node as FunctionDeclaration) : (skipped = true, undefined) : undefined).body; From 8e74ac8025e93781115c6b133a82086e308994be Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jul 2017 10:11:44 -0700 Subject: [PATCH 3/5] fix tests --- src/compiler/checker.ts | 8 ++--- .../unittests/checkerPublicRelationships.ts | 32 ++++++++----------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8334e40715cf2..b04bec7cee57b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -272,16 +272,16 @@ namespace ts { getUnknownType: () => unknownType, getStringLiteralType: (text: string) => { /* tslint:disable:no-null-keyword */ - Debug.assert(text !== undefined && text !== null); + Debug.assert(text !== undefined && text !== null, "Argument to getStringLiteralType was null or undefined"); /* tslint:enable:no-null-keyword */ - Debug.assert(typeof text === "string"); + Debug.assert(typeof text === "string", "Argument to getStringLiteralType not a string"); return getLiteralType(text); }, getNumberLiteralType: (number: number) => { /* tslint:disable:no-null-keyword */ - Debug.assert(number !== undefined && number !== null); + Debug.assert(number !== undefined && number !== null, "Argument to getNumberLiteralType was null or undefined"); /* tslint:enable:no-null-keyword */ - Debug.assert(typeof number === "number"); + Debug.assert(typeof number === "number", "Argument to getStringLiteralType not a number"); return getLiteralType(number); }, getFalseType: () => falseType, diff --git a/src/harness/unittests/checkerPublicRelationships.ts b/src/harness/unittests/checkerPublicRelationships.ts index 176155d69ec68..281d0e2a2766a 100644 --- a/src/harness/unittests/checkerPublicRelationships.ts +++ b/src/harness/unittests/checkerPublicRelationships.ts @@ -87,42 +87,38 @@ namespace ts { }); it("can get string literal types", () => { - assert((checker.getLiteralType("foobar") as LiteralType).value === "foobar"); + assert(checker.getStringLiteralType("foobar").value === "foobar"); }); it("can get numeber literal types", () => { - assert((checker.getLiteralType(42) as LiteralType).value === "42"); + assert(checker.getNumberLiteralType(42).value === 42); }); it("doesn't choke on exceptional input to literal type getters", () => { - assert.equal((checker.getLiteralType("") as LiteralType).value, ""); - assert.throws(() => checker.getLiteralType(/*content*/ undefined), Error, "Debug Failure. False expression:"); + assert.equal(checker.getStringLiteralType("").value, ""); + assert.throws(() => checker.getStringLiteralType(/*content*/ undefined), Error, "Argument to getStringLiteralType was null or undefined"); /* tslint:disable:no-null-keyword */ - assert.throws(() => checker.getLiteralType(/*content*/ null), Error, "Debug Failure. False expression:"); + assert.throws(() => checker.getNumberLiteralType(/*content*/ null), Error, "Argument to getNumberLiteralType was null or undefined"); /* tslint:enable:no-null-keyword */ let hugeStringLiteral = map(new Array(2 ** 16 - 1), () => "a").join(); - assert.equal((checker.getLiteralType(hugeStringLiteral) as LiteralType).value, hugeStringLiteral); + assert.equal(checker.getStringLiteralType(hugeStringLiteral).value, hugeStringLiteral); hugeStringLiteral = undefined; - - assert.throws(() => checker.getLiteralType(/*content*/ undefined), Error, "Debug Failure. False expression:"); - /* tslint:disable:no-null-keyword */ - assert.throws(() => checker.getLiteralType(/*content*/ null), Error, "Debug Failure. False expression:"); - /* tslint:enable:no-null-keyword */ - const sanityChecks = ["000", "0b0", "0x0", "0.0", "0e-0", "-010", "-0b10", "-0x10", "-0o10", "-10.0", "-1e-1", "NaN", "Infinity", "-Infinity"]; forEach(sanityChecks, num => { - assert.equal((checker.getLiteralType(num) as LiteralType).value, num, `${num} did not match.`); + assert.equal(checker.getStringLiteralType(num).value, num, `${num} did not match.`); }); - const insanityChecks = [[0, "0"], [0b0, "0"], [-10, "-10"], [NaN, "NaN"], [Infinity, "Infinity"], [-Infinity, "-Infinity"]]; - forEach(insanityChecks, ([num, expected]) => { - assert.equal((checker.getLiteralType(num as any) as LiteralType).value, expected, `${JSON.stringify(num)} should be ${expected}`); + const insanityChecks = [0, 0b0, -10, Infinity, -Infinity]; + forEach(insanityChecks, (num) => { + assert.equal(checker.getNumberLiteralType(num).value, num, `${JSON.stringify(num)} should be ${num}`); }); + assert.isNaN(checker.getNumberLiteralType(NaN).value); + const instabilityChecks = [{ foo: 42 }, new Date(42), [42], new Number(42), new String("42")]; forEach(instabilityChecks, (bad) => { - assert.throws(() => checker.getLiteralType(bad as any)); + assert.throws(() => checker.getStringLiteralType(bad as any)); }); }); @@ -152,7 +148,7 @@ namespace ts { assert.notEqual(checker.lookupGlobalType("Function"), innerFunction, "Inner function type should be different than global"); const brandNameType = checker.getTypeOfSymbol(innerFunction.getProperty("myBrand")); assert.notEqual(brandNameType, checker.getUnknownType(), "Brand type on inner function should not be unknown"); - assert.equal(brandNameType, checker.getLiteralType(42), "Brand type should be 42"); + assert.equal(brandNameType, checker.getNumberLiteralType(42), "Brand type should be 42"); let skipped = false; const functionBody2 = forEachChild(program.getSourceFile("test.ts"), node => node.kind === SyntaxKind.FunctionDeclaration ? skipped ? (node as FunctionDeclaration) : (skipped = true, undefined) : undefined).body; From fb946ad4400a72f4733969054fe7a9882f4a4e82 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 11 Sep 2017 18:59:40 -0700 Subject: [PATCH 4/5] Add non-primitive type --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 1 + src/harness/unittests/checkerPublicRelationships.ts | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b04bec7cee57b..9ff9055f9ada0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -286,6 +286,7 @@ namespace ts { }, getFalseType: () => falseType, getTrueType: () => trueType, + getNonPrimitiveType: () => nonPrimitiveType, }; const tupleTypes: GenericType[] = []; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7d02d434ad460..30b83e9581714 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2782,6 +2782,7 @@ namespace ts { getNumberLiteralType(num: number): LiteralType; getFalseType(): Type; getTrueType(): Type; + getNonPrimitiveType(): Type; // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; diff --git a/src/harness/unittests/checkerPublicRelationships.ts b/src/harness/unittests/checkerPublicRelationships.ts index 281d0e2a2766a..9b901a8884b73 100644 --- a/src/harness/unittests/checkerPublicRelationships.ts +++ b/src/harness/unittests/checkerPublicRelationships.ts @@ -86,6 +86,10 @@ namespace ts { assert(checker.getFalseType() !== checker.getTrueType()); }); + it("can get the non-primitive type", () => { + assert(checker.getNonPrimitiveType().flags & TypeFlags.NonPrimitive); + }); + it("can get string literal types", () => { assert(checker.getStringLiteralType("foobar").value === "foobar"); }); From 04e27ccad8e4501fb9b9f34ccacaef208e58757f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 6 Nov 2017 16:21:06 -0800 Subject: [PATCH 5/5] Psuedo-merge-conflict fixes --- Jakefile.js | 1 - src/compiler/checker.ts | 22 ++--- src/compiler/types.ts | 9 -- src/harness/tsconfig.json | 1 - .../reference/api/tsserverlibrary.d.ts | 92 +++++++++++++++++++ tests/baselines/reference/api/typescript.d.ts | 92 +++++++++++++++++++ 6 files changed, 190 insertions(+), 27 deletions(-) diff --git a/Jakefile.js b/Jakefile.js index 826c4a31ef3bf..a6f0bf54dc33a 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -128,7 +128,6 @@ var harnessSources = harnessCoreSources.concat([ "transpile.ts", "reuseProgramStructure.ts", "textStorage.ts", - "cachingInServerLSHost.ts", "checkerPublicRelationships.ts", "moduleResolution.ts", "tsconfigParsing.ts", diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a8c09c933a7d0..78767c069542c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -272,24 +272,14 @@ namespace ts { return symbol ? getTypeOfSymbol(symbol) : unknownType; }, lookupTypeAt: (name, node) => { - const symbol = resolveName(node, escapeLeadingUnderscores(name), SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined); + const symbol = resolveName(node, escapeLeadingUnderscores(name), SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined, /*isUse*/ false); return symbol ? getDeclaredTypeOfSymbol(symbol) : unknownType; }, lookupValueTypeAt: (name, node) => { - const symbol = resolveName(node, escapeLeadingUnderscores(name), SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined); + const symbol = resolveName(node, escapeLeadingUnderscores(name), SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined, /*isUse*/ false); return symbol ? getTypeOfSymbol(symbol) : unknownType; }, getTypeOfSymbol, - - getAnyType: () => anyType, - getStringType: () => stringType, - getNumberType: () => numberType, - getBooleanType: () => booleanType, - getVoidType: () => voidType, - getUndefinedType: () => undefinedType, - getNullType: () => nullType, - getESSymbolType: () => esSymbolType, - getNeverType: () => neverType, getUnknownType: () => unknownType, getStringLiteralType: (text: string) => { /* tslint:disable:no-null-keyword */ @@ -298,12 +288,12 @@ namespace ts { Debug.assert(typeof text === "string", "Argument to getStringLiteralType not a string"); return getLiteralType(text); }, - getNumberLiteralType: (number: number) => { + getNumberLiteralType: (num: number) => { /* tslint:disable:no-null-keyword */ - Debug.assert(number !== undefined && number !== null, "Argument to getNumberLiteralType was null or undefined"); + Debug.assert(num !== undefined && num !== null, "Argument to getNumberLiteralType was null or undefined"); /* tslint:enable:no-null-keyword */ - Debug.assert(typeof number === "number", "Argument to getStringLiteralType not a number"); - return getLiteralType(number); + Debug.assert(typeof num === "number", "Argument to getStringLiteralType not a number"); + return getLiteralType(num); }, getFalseType: () => falseType, getTrueType: () => trueType, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 35b5331482d48..8173da48e77ed 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2725,15 +2725,6 @@ namespace ts { getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; /* @internal */ getBaseConstraintOfType(type: Type): Type | undefined; - /* @internal */ getAnyType(): Type; - /* @internal */ getStringType(): Type; - /* @internal */ getNumberType(): Type; - /* @internal */ getBooleanType(): Type; - /* @internal */ getVoidType(): Type; - /* @internal */ getUndefinedType(): Type; - /* @internal */ getNullType(): Type; - /* @internal */ getESSymbolType(): Type; - /* @internal */ getNeverType(): Type; /* @internal */ getUnionType(types: Type[], subtypeReduction?: boolean): Type; /* @internal */ createArrayType(elementType: Type): Type; /* @internal */ createPromiseType(type: Type): Type; diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index d1c1929d5b370..5002509ad02bc 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -115,7 +115,6 @@ "./unittests/convertToBase64.ts", "./unittests/transpile.ts", "./unittests/reuseProgramStructure.ts", - "./unittests/cachingInServerLSHost.ts", "./unittests/checkerPublicRelationships.ts", "./unittests/moduleResolution.ts", "./unittests/tsconfigParsing.ts", diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 85895ffbce428..352d5b2b5bb42 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1778,6 +1778,98 @@ declare namespace ts { getApparentType(type: Type): Type; getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined; getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; + /** + * Two types are considered identical when + * - they are both the `any` type, + * - they are the same primitive type, + * - they are the same type parameter, + * - they are union types with identical sets of constituent types, or + * - they are intersection types with identical sets of constituent types, or + * - they are object types with identical sets of members. + * + * This relationship is bidirectional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.2) for more information. + */ + isIdenticalTo(a: Type, b: Type): boolean; + /** + * `a` is a ___subtype___ of `b` (and `b` is a ___supertype___ of `a`) if `a` has no excess properties with respect to `b`, + * and one of the following is true: + * - `a` and `b` are identical types. + * - `b` is the `any` type. + * - `a` is the `undefined` type. + * - `a` is the `null` type and `b` is _not_ the `undefined` type. + * - `a` is an enum type and `b` is the primitive type `number`. + * - `a` is a string literal type and `b` is the primitive type `string`. + * - `a` is a union type and each constituient type of `b` is a subtype of `b`. + * - `a` is an intersection type and at least one constituent type of `a` is a subtype of `b`. + * - `b` is a union type and `a` is a subtype of at least one constituent type of `b`. + * - `b` is an intersection type and `a` is a subtype of each constituent type of `b`. + * - `a` is a type parameter and the constraint of `a` is a subtype of `b`. + * - `a` has a subset of the structural members of `b`. + * + * This relationship is directional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.3) for more information. + */ + isSubtypeOf(a: Type, b: Type): boolean; + /** + * The assignable relationship differs only from the subtype relationship in that: + * - the `any` type is assignable to, but not a subtype of, all types + * - the primitive type `number` is assignable to, but not a subtype of, all enum types, and + * - an object type without a particular property is assignable to an object type in which that property is optional. + * + * This relationship is directional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.4) for more information. + */ + isAssignableTo(a: Type, b: Type): boolean; + /** + * True if `a` is assignable to `b`, or `b` is assignable to `a`. Additionally, all unions with + * overlapping constituient types are comparable, and unit types in the same domain are comparable. + * This relationship is bidirectional. + */ + isComparableTo(a: Type, b: Type): boolean; + /** + * Not a formal relationship - returns true if a is an instantiation of the generic type b + */ + isInstantiationOf(a: GenericType, b: GenericType): boolean; + /** + * Returns the declared type of the globally named symbol with meaning SymbolFlags.Type + * Returns the unknown type on failure. + */ + lookupGlobalType(name: string): Type; + /** + * Returns the declared type of the globally named symbol with meaning SymbolFlags.Value + * Returns the unknown type on failure. + */ + lookupGlobalValueType(name: string): Type; + /** + * Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Type + * Returns the unknown type on failure. + */ + lookupTypeAt(name: string, position: Node): Type; + /** + * Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Value + * Returns the unknown type on failure. + */ + lookupValueTypeAt(name: string, position: Node): Type; + /** + * Returns the type of a symbol + */ + getTypeOfSymbol(symbol: Symbol): Type; + getAnyType(): Type; + getStringType(): Type; + getNumberType(): Type; + getBooleanType(): Type; + getVoidType(): Type; + getUndefinedType(): Type; + getNullType(): Type; + getESSymbolType(): Type; + getNeverType(): Type; + getUnknownType(): Type; + getStringLiteralType(text: string): LiteralType; + getNumberLiteralType(num: number): LiteralType; + getFalseType(): Type; + getTrueType(): Type; + getNonPrimitiveType(): Type; } enum NodeBuilderFlags { None = 0, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fd58e72181ab1..790c4fa8e3df8 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1778,6 +1778,98 @@ declare namespace ts { getApparentType(type: Type): Type; getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined; getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; + /** + * Two types are considered identical when + * - they are both the `any` type, + * - they are the same primitive type, + * - they are the same type parameter, + * - they are union types with identical sets of constituent types, or + * - they are intersection types with identical sets of constituent types, or + * - they are object types with identical sets of members. + * + * This relationship is bidirectional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.2) for more information. + */ + isIdenticalTo(a: Type, b: Type): boolean; + /** + * `a` is a ___subtype___ of `b` (and `b` is a ___supertype___ of `a`) if `a` has no excess properties with respect to `b`, + * and one of the following is true: + * - `a` and `b` are identical types. + * - `b` is the `any` type. + * - `a` is the `undefined` type. + * - `a` is the `null` type and `b` is _not_ the `undefined` type. + * - `a` is an enum type and `b` is the primitive type `number`. + * - `a` is a string literal type and `b` is the primitive type `string`. + * - `a` is a union type and each constituient type of `b` is a subtype of `b`. + * - `a` is an intersection type and at least one constituent type of `a` is a subtype of `b`. + * - `b` is a union type and `a` is a subtype of at least one constituent type of `b`. + * - `b` is an intersection type and `a` is a subtype of each constituent type of `b`. + * - `a` is a type parameter and the constraint of `a` is a subtype of `b`. + * - `a` has a subset of the structural members of `b`. + * + * This relationship is directional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.3) for more information. + */ + isSubtypeOf(a: Type, b: Type): boolean; + /** + * The assignable relationship differs only from the subtype relationship in that: + * - the `any` type is assignable to, but not a subtype of, all types + * - the primitive type `number` is assignable to, but not a subtype of, all enum types, and + * - an object type without a particular property is assignable to an object type in which that property is optional. + * + * This relationship is directional. + * See [here](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.11.4) for more information. + */ + isAssignableTo(a: Type, b: Type): boolean; + /** + * True if `a` is assignable to `b`, or `b` is assignable to `a`. Additionally, all unions with + * overlapping constituient types are comparable, and unit types in the same domain are comparable. + * This relationship is bidirectional. + */ + isComparableTo(a: Type, b: Type): boolean; + /** + * Not a formal relationship - returns true if a is an instantiation of the generic type b + */ + isInstantiationOf(a: GenericType, b: GenericType): boolean; + /** + * Returns the declared type of the globally named symbol with meaning SymbolFlags.Type + * Returns the unknown type on failure. + */ + lookupGlobalType(name: string): Type; + /** + * Returns the declared type of the globally named symbol with meaning SymbolFlags.Value + * Returns the unknown type on failure. + */ + lookupGlobalValueType(name: string): Type; + /** + * Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Type + * Returns the unknown type on failure. + */ + lookupTypeAt(name: string, position: Node): Type; + /** + * Returns the declared type of the named symbol lexically at the position specified with meaning SymbolFlags.Value + * Returns the unknown type on failure. + */ + lookupValueTypeAt(name: string, position: Node): Type; + /** + * Returns the type of a symbol + */ + getTypeOfSymbol(symbol: Symbol): Type; + getAnyType(): Type; + getStringType(): Type; + getNumberType(): Type; + getBooleanType(): Type; + getVoidType(): Type; + getUndefinedType(): Type; + getNullType(): Type; + getESSymbolType(): Type; + getNeverType(): Type; + getUnknownType(): Type; + getStringLiteralType(text: string): LiteralType; + getNumberLiteralType(num: number): LiteralType; + getFalseType(): Type; + getTrueType(): Type; + getNonPrimitiveType(): Type; } enum NodeBuilderFlags { None = 0,