Merge pull request #13903 from Microsoft/jsPropertyWidening

Widen special JS property declarations to match regular property declarations
This commit is contained in:
Mohamed Hegazy
2017-02-09 15:08:35 -08:00
committed by GitHub
9 changed files with 143 additions and 31 deletions

View File

@@ -3259,7 +3259,7 @@ namespace ts {
type;
}
function getTypeForVariableLikeDeclarationFromJSDocComment(declaration: VariableLikeDeclaration) {
function getTypeForDeclarationFromJSDocComment(declaration: Node ) {
const jsdocType = getJSDocType(declaration);
if (jsdocType) {
return getTypeFromTypeNode(jsdocType);
@@ -3287,7 +3287,7 @@ namespace ts {
// If this is a variable in a JavaScript file, then use the JSDoc type (if it has
// one as its type), otherwise fallback to the below standard TS codepaths to
// try to figure it out.
const type = getTypeForVariableLikeDeclarationFromJSDocComment(declaration);
const type = getTypeForDeclarationFromJSDocComment(declaration);
if (type && type !== unknownType) {
return type;
}
@@ -3382,6 +3382,27 @@ namespace ts {
return undefined;
}
// Return the inferred type for a variable, parameter, or property declaration
function getTypeForJSSpecialPropertyDeclaration(declaration: Declaration): Type {
const expression = declaration.kind === SyntaxKind.BinaryExpression ? <BinaryExpression>declaration :
declaration.kind === SyntaxKind.PropertyAccessExpression ? <BinaryExpression>getAncestor(declaration, SyntaxKind.BinaryExpression) :
undefined;
if (!expression) {
return unknownType;
}
if (expression.flags & NodeFlags.JavaScriptFile) {
// If there is a JSDoc type, use it
const type = getTypeForDeclarationFromJSDocComment(expression.parent);
if (type && type !== unknownType) {
return getWidenedType(type);
}
}
return getWidenedLiteralType(checkExpressionCached(expression.right));
}
// Return the type implied by a binding pattern element. This is the type of the initializer of the element if
// one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding
// pattern. Otherwise, it is the type any.
@@ -3536,18 +3557,7 @@ namespace ts {
// * className.prototype.method = expr
if (declaration.kind === SyntaxKind.BinaryExpression ||
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
// Use JS Doc type if present on parent expression statement
if (declaration.flags & NodeFlags.JavaScriptFile) {
const jsdocType = getJSDocType(declaration.parent);
if (jsdocType) {
return links.type = getTypeFromTypeNode(jsdocType);
}
}
const declaredTypes = map(symbol.declarations,
decl => decl.kind === SyntaxKind.BinaryExpression ?
checkExpressionCached((<BinaryExpression>decl).right) :
checkExpressionCached((<BinaryExpression>decl.parent).right));
type = getUnionType(declaredTypes, /*subtypeReduction*/ true);
type = getWidenedType(getUnionType(map(symbol.declarations, getTypeForJSSpecialPropertyDeclaration), /*subtypeReduction*/ true));
}
else {
type = getWidenedTypeForVariableLikeDeclaration(<VariableLikeDeclaration>declaration, /*reportErrors*/ true);
@@ -3590,7 +3600,7 @@ namespace ts {
const setter = <AccessorDeclaration>getDeclarationOfKind(symbol, SyntaxKind.SetAccessor);
if (getter && getter.flags & NodeFlags.JavaScriptFile) {
const jsDocType = getTypeForVariableLikeDeclarationFromJSDocComment(getter);
const jsDocType = getTypeForDeclarationFromJSDocComment(getter);
if (jsDocType) {
return links.type = jsDocType;
}

View File

@@ -0,0 +1,17 @@
tests/cases/compiler/bar.ts(2,1): error TS2322: Type '"string"' is not assignable to type 'number'.
==== tests/cases/compiler/foo.js (0 errors) ====
class C {
constructor () {
this.p = 0;
}
}
==== tests/cases/compiler/bar.ts (1 errors) ====
(new C()).p = "string";
~~~~~~~~~~~
!!! error TS2322: Type '"string"' is not assignable to type 'number'.

View File

@@ -0,0 +1,18 @@
tests/cases/compiler/bar.ts(2,18): error TS2345: Argument of type '"string"' is not assignable to parameter of type 'number'.
==== tests/cases/compiler/foo.js (0 errors) ====
class C {
constructor() {
/** @type {number[]}*/
this.p = [];
}
}
==== tests/cases/compiler/bar.ts (1 errors) ====
(new C()).p.push("string");
~~~~~~~~
!!! error TS2345: Argument of type '"string"' is not assignable to parameter of type 'number'.

View File

@@ -0,0 +1,22 @@
tests/cases/compiler/bar.ts(2,1): error TS2322: Type '"string"' is not assignable to type 'number'.
==== tests/cases/compiler/foo.js (0 errors) ====
class C {
constructor() {
if (cond) {
this.p = null;
}
else {
this.p = 0;
}
}
}
==== tests/cases/compiler/bar.ts (1 errors) ====
(new C()).p = "string"; // Error
~~~~~~~~~~~
!!! error TS2322: Type '"string"' is not assignable to type 'number'.

View File

@@ -15,39 +15,39 @@ function MyClass() {
* @returns {MyClass}
*/
MyClass.prototype.optionalParam = function(required, notRequired) {
>MyClass.prototype.optionalParam = function(required, notRequired) { return this;} : (required: string, notRequired?: string) => { prop: null; optionalParam: any; }
>MyClass.prototype.optionalParam = function(required, notRequired) { return this;} : (required: string, notRequired?: string) => { prop: any; optionalParam: any; }
>MyClass.prototype.optionalParam : any
>MyClass.prototype : any
>MyClass : () => void
>prototype : any
>optionalParam : any
>function(required, notRequired) { return this;} : (required: string, notRequired?: string) => { prop: null; optionalParam: any; }
>function(required, notRequired) { return this;} : (required: string, notRequired?: string) => { prop: any; optionalParam: any; }
>required : string
>notRequired : string
return this;
>this : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>this : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
};
let pInst = new MyClass();
>pInst : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>new MyClass() : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>new MyClass() : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>MyClass : () => void
let c1 = pInst.optionalParam('hello')
>c1 : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam('hello') : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam : (required: string, notRequired?: string) => { prop: null; optionalParam: any; }
>pInst : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>optionalParam : (required: string, notRequired?: string) => { prop: null; optionalParam: any; }
>c1 : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam('hello') : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam : (required: string, notRequired?: string) => { prop: any; optionalParam: any; }
>pInst : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>optionalParam : (required: string, notRequired?: string) => { prop: any; optionalParam: any; }
>'hello' : "hello"
let c2 = pInst.optionalParam('hello', null)
>c2 : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam('hello', null) : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam : (required: string, notRequired?: string) => { prop: null; optionalParam: any; }
>pInst : { prop: null; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>optionalParam : (required: string, notRequired?: string) => { prop: null; optionalParam: any; }
>c2 : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam('hello', null) : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>pInst.optionalParam : (required: string, notRequired?: string) => { prop: any; optionalParam: any; }
>pInst : { prop: any; optionalParam: (required: string, notRequired?: string) => typeof MyClass; }
>optionalParam : (required: string, notRequired?: string) => { prop: any; optionalParam: any; }
>'hello' : "hello"
>null : null

View File

@@ -0,0 +1,13 @@
// @allowJs: true
// @noEmit: true
// @filename: foo.js
class C {
constructor () {
this.p = 0;
}
}
// @filename: bar.ts
(new C()).p = "string";

View File

@@ -0,0 +1,14 @@
// @allowJs: true
// @noEmit: true
// @filename: foo.js
class C {
constructor() {
/** @type {number[]}*/
this.p = [];
}
}
// @filename: bar.ts
(new C()).p.push("string");

View File

@@ -0,0 +1,18 @@
// @allowJs: true
// @noEmit: true
// @filename: foo.js
class C {
constructor() {
if (cond) {
this.p = null;
}
else {
this.p = 0;
}
}
}
// @filename: bar.ts
(new C()).p = "string"; // Error

View File

@@ -12,4 +12,4 @@
//// let x = new Person(100);
//// x.canVote/**/;
verify.quickInfoAt("", "(property) Person.canVote: true | 23");
verify.quickInfoAt("", "(property) Person.canVote: number | boolean");