Allow special assignments to have a contextual type of their declared type if present (#26802)

* Allow special assignments to have a contextual type of their declared type if present

* Expand change to cover all js special assignments

* Remove extraneous line
This commit is contained in:
Wesley Wigham
2018-09-04 15:58:18 -07:00
committed by GitHub
parent 262ea5b06e
commit f1370ecd54
10 changed files with 393 additions and 18 deletions

View File

@@ -2080,8 +2080,8 @@ namespace ts {
if (isInJavaScriptFile(node) &&
file.commonJsModuleIndicator &&
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
!lookupSymbolForNameWorker(container, "module" as __String)) {
declareSymbol(container.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
!lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
}
break;

View File

@@ -16122,7 +16122,14 @@ namespace ts {
const { left, operatorToken, right } = binaryExpression;
switch (operatorToken.kind) {
case SyntaxKind.EqualsToken:
return node === right && isContextSensitiveAssignment(binaryExpression) ? getTypeOfExpression(left) : undefined;
if (node !== right) {
return undefined;
}
const contextSensitive = getIsContextSensitiveAssignmentOrContextType(binaryExpression);
if (!contextSensitive) {
return undefined;
}
return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive;
case SyntaxKind.BarBarToken:
// When an || expression has a contextual type, the operands are contextually typed by that type. When an ||
// expression has no contextual type, the right operand is contextually typed by the type of the left operand,
@@ -16140,7 +16147,7 @@ namespace ts {
// In an assignment expression, the right operand is contextually typed by the type of the left operand.
// Don't do this for special property assignments unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
function isContextSensitiveAssignment(binaryExpression: BinaryExpression): boolean {
function getIsContextSensitiveAssignmentOrContextType(binaryExpression: BinaryExpression): boolean | Type {
const kind = getSpecialPropertyAssignmentKind(binaryExpression);
switch (kind) {
case SpecialPropertyAssignmentKind.None:
@@ -16159,29 +16166,44 @@ namespace ts {
if (!decl) {
return false;
}
if (isInJavaScriptFile(decl)) {
return !!getJSDocTypeTag(decl);
const lhs = binaryExpression.left as PropertyAccessExpression;
const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
if (overallAnnotation) {
return getTypeFromTypeNode(overallAnnotation);
}
else if (isIdentifier((binaryExpression.left as PropertyAccessExpression).expression)) {
const id = (binaryExpression.left as PropertyAccessExpression).expression as Identifier;
else if (isIdentifier(lhs.expression)) {
const id = lhs.expression;
const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true);
return !isFunctionSymbol(parentSymbol);
if (parentSymbol && isFunctionSymbol(parentSymbol)) {
const annotated = getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration);
if (annotated) {
const type = getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), lhs.name.escapedText);
return type || false;
}
return false;
}
}
return true;
return !isInJavaScriptFile(decl);
}
case SpecialPropertyAssignmentKind.ModuleExports:
case SpecialPropertyAssignmentKind.ThisProperty:
if (!binaryExpression.symbol ||
binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration)) {
return true;
if (!binaryExpression.symbol) return true;
if (binaryExpression.symbol.valueDeclaration) {
const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
if (annotated) {
const type = getTypeFromTypeNode(annotated);
if (type) {
return type;
}
}
}
if (kind === SpecialPropertyAssignmentKind.ModuleExports) return false;
const thisAccess = binaryExpression.left as PropertyAccessExpression;
if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
return false;
}
const thisType = checkThisExpression(thisAccess.expression);
return thisType && !!getPropertyOfType(thisType, thisAccess.name.escapedText);
case SpecialPropertyAssignmentKind.ModuleExports:
return !binaryExpression.symbol || binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration);
return thisType && getTypeOfPropertyOfContextualType(thisType, thisAccess.name.escapedText) || false;
default:
return Debug.assertNever(kind);
}

View File

@@ -0,0 +1,22 @@
//// [expandoFunctionContextualTypes.ts]
interface MyComponentProps {
color: "red" | "blue"
}
interface StatelessComponent<P> {
(): any;
defaultProps?: Partial<P>;
}
const MyComponent: StatelessComponent<MyComponentProps> = () => null as any;
MyComponent.defaultProps = {
color: "red"
};
//// [expandoFunctionContextualTypes.js]
var MyComponent = function () { return null; };
MyComponent.defaultProps = {
color: "red"
};

View File

@@ -0,0 +1,34 @@
=== tests/cases/compiler/expandoFunctionContextualTypes.ts ===
interface MyComponentProps {
>MyComponentProps : Symbol(MyComponentProps, Decl(expandoFunctionContextualTypes.ts, 0, 0))
color: "red" | "blue"
>color : Symbol(MyComponentProps.color, Decl(expandoFunctionContextualTypes.ts, 0, 28))
}
interface StatelessComponent<P> {
>StatelessComponent : Symbol(StatelessComponent, Decl(expandoFunctionContextualTypes.ts, 2, 1))
>P : Symbol(P, Decl(expandoFunctionContextualTypes.ts, 4, 29))
(): any;
defaultProps?: Partial<P>;
>defaultProps : Symbol(StatelessComponent.defaultProps, Decl(expandoFunctionContextualTypes.ts, 5, 12))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(expandoFunctionContextualTypes.ts, 4, 29))
}
const MyComponent: StatelessComponent<MyComponentProps> = () => null as any;
>MyComponent : Symbol(MyComponent, Decl(expandoFunctionContextualTypes.ts, 9, 5))
>StatelessComponent : Symbol(StatelessComponent, Decl(expandoFunctionContextualTypes.ts, 2, 1))
>MyComponentProps : Symbol(MyComponentProps, Decl(expandoFunctionContextualTypes.ts, 0, 0))
MyComponent.defaultProps = {
>MyComponent.defaultProps : Symbol(StatelessComponent.defaultProps, Decl(expandoFunctionContextualTypes.ts, 5, 12))
>MyComponent : Symbol(MyComponent, Decl(expandoFunctionContextualTypes.ts, 9, 5))
>defaultProps : Symbol(StatelessComponent.defaultProps, Decl(expandoFunctionContextualTypes.ts, 5, 12))
color: "red"
>color : Symbol(color, Decl(expandoFunctionContextualTypes.ts, 11, 28))
};

View File

@@ -0,0 +1,31 @@
=== tests/cases/compiler/expandoFunctionContextualTypes.ts ===
interface MyComponentProps {
color: "red" | "blue"
>color : "red" | "blue"
}
interface StatelessComponent<P> {
(): any;
defaultProps?: Partial<P>;
>defaultProps : Partial<P>
}
const MyComponent: StatelessComponent<MyComponentProps> = () => null as any;
>MyComponent : StatelessComponent<MyComponentProps>
>() => null as any : { (): any; defaultProps: { color: "red"; }; }
>null as any : any
>null : null
MyComponent.defaultProps = {
>MyComponent.defaultProps = { color: "red"} : { color: "red"; }
>MyComponent.defaultProps : Partial<MyComponentProps>
>MyComponent : StatelessComponent<MyComponentProps>
>defaultProps : Partial<MyComponentProps>
>{ color: "red"} : { color: "red"; }
color: "red"
>color : "red"
>"red" : "red"
};

View File

@@ -0,0 +1,86 @@
=== tests/cases/compiler/input.js ===
/** @typedef {{ color: "red" | "blue" }} MyComponentProps */
/**
* @template P
* @typedef {{ (): any; defaultProps?: Partial<P> }} StatelessComponent */
/**
* @type {StatelessComponent<MyComponentProps>}
*/
const MyComponent = () => /* @type {any} */(null);
>MyComponent : Symbol(MyComponent, Decl(input.js, 9, 5))
MyComponent.defaultProps = {
>MyComponent.defaultProps : Symbol(defaultProps, Decl(input.js, 4, 23))
>MyComponent : Symbol(MyComponent, Decl(input.js, 9, 5))
>defaultProps : Symbol(defaultProps, Decl(input.js, 4, 23))
color: "red"
>color : Symbol(color, Decl(input.js, 11, 28))
};
const MyComponent2 = () => null;
>MyComponent2 : Symbol(MyComponent2, Decl(input.js, 15, 5))
/**
* @type {MyComponentProps}
*/
MyComponent2.defaultProps = {
>MyComponent2.defaultProps : Symbol(MyComponent2.defaultProps, Decl(input.js, 15, 32))
>MyComponent2 : Symbol(MyComponent2, Decl(input.js, 15, 5))
>defaultProps : Symbol(MyComponent2.defaultProps, Decl(input.js, 15, 32))
color: "red"
>color : Symbol(color, Decl(input.js, 20, 29))
}
/**
* @type {StatelessComponent<MyComponentProps>}
*/
const check = MyComponent2;
>check : Symbol(check, Decl(input.js, 27, 5))
>MyComponent2 : Symbol(MyComponent2, Decl(input.js, 15, 5))
/**
*
* @param {{ props: MyComponentProps }} p
*/
function expectLiteral(p) {}
>expectLiteral : Symbol(expectLiteral, Decl(input.js, 27, 27))
>p : Symbol(p, Decl(input.js, 33, 23))
function foo() {
>foo : Symbol(foo, Decl(input.js, 33, 28))
/**
* @type {MyComponentProps}
*/
this.props = { color: "red" };
>props : Symbol(foo.props, Decl(input.js, 35, 16))
>color : Symbol(color, Decl(input.js, 39, 18))
expectLiteral(this);
>expectLiteral : Symbol(expectLiteral, Decl(input.js, 27, 27))
}
/**
* @type {MyComponentProps}
*/
module.exports = {
>module.exports : Symbol("tests/cases/compiler/input", Decl(input.js, 0, 0))
>module : Symbol(export=, Decl(input.js, 42, 1))
>exports : Symbol(export=, Decl(input.js, 42, 1))
color: "red"
>color : Symbol(color, Decl(input.js, 47, 18))
}
expectLiteral({ props: module.exports });
>expectLiteral : Symbol(expectLiteral, Decl(input.js, 27, 27))
>props : Symbol(props, Decl(input.js, 51, 15))
>module.exports : Symbol("tests/cases/compiler/input", Decl(input.js, 0, 0))
>module : Symbol(module, Decl(input.js, 42, 1), Decl(input.js, 51, 22))
>exports : Symbol("tests/cases/compiler/input", Decl(input.js, 0, 0))

View File

@@ -0,0 +1,109 @@
=== tests/cases/compiler/input.js ===
/** @typedef {{ color: "red" | "blue" }} MyComponentProps */
/**
* @template P
* @typedef {{ (): any; defaultProps?: Partial<P> }} StatelessComponent */
/**
* @type {StatelessComponent<MyComponentProps>}
*/
const MyComponent = () => /* @type {any} */(null);
>MyComponent : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; }
>() => /* @type {any} */(null) : { (): any; defaultProps: { color: "red"; }; }
>(null) : null
>null : null
MyComponent.defaultProps = {
>MyComponent.defaultProps = { color: "red"} : { color: "red"; }
>MyComponent.defaultProps : Partial<{ color: "red" | "blue"; }>
>MyComponent : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; }
>defaultProps : Partial<{ color: "red" | "blue"; }>
>{ color: "red"} : { color: "red"; }
color: "red"
>color : "red"
>"red" : "red"
};
const MyComponent2 = () => null;
>MyComponent2 : { (): any; defaultProps: { color: "red" | "blue"; }; }
>() => null : { (): any; defaultProps: { color: "red" | "blue"; }; }
>null : null
/**
* @type {MyComponentProps}
*/
MyComponent2.defaultProps = {
>MyComponent2.defaultProps = { color: "red"} : { color: "red"; }
>MyComponent2.defaultProps : { color: "red" | "blue"; }
>MyComponent2 : { (): any; defaultProps: { color: "red" | "blue"; }; }
>defaultProps : { color: "red" | "blue"; }
>{ color: "red"} : { color: "red"; }
color: "red"
>color : "red"
>"red" : "red"
}
/**
* @type {StatelessComponent<MyComponentProps>}
*/
const check = MyComponent2;
>check : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; }
>MyComponent2 : { (): any; defaultProps: { color: "red" | "blue"; }; }
/**
*
* @param {{ props: MyComponentProps }} p
*/
function expectLiteral(p) {}
>expectLiteral : (p: { props: { color: "red" | "blue"; }; }) => void
>p : { props: { color: "red" | "blue"; }; }
function foo() {
>foo : typeof foo
/**
* @type {MyComponentProps}
*/
this.props = { color: "red" };
>this.props = { color: "red" } : { color: "red"; }
>this.props : any
>this : any
>props : any
>{ color: "red" } : { color: "red"; }
>color : "red"
>"red" : "red"
expectLiteral(this);
>expectLiteral(this) : void
>expectLiteral : (p: { props: { color: "red" | "blue"; }; }) => void
>this : any
}
/**
* @type {MyComponentProps}
*/
module.exports = {
>module.exports = { color: "red"} : { color: "red" | "blue"; }
>module.exports : { color: "red" | "blue"; }
>module : { "tests/cases/compiler/input": { color: "red" | "blue"; }; }
>exports : { color: "red" | "blue"; }
>{ color: "red"} : { color: "red"; }
color: "red"
>color : "red"
>"red" : "red"
}
expectLiteral({ props: module.exports });
>expectLiteral({ props: module.exports }) : void
>expectLiteral : (p: { props: { color: "red" | "blue"; }; }) => void
>{ props: module.exports } : { props: { color: "red" | "blue"; }; }
>props : { color: "red" | "blue"; }
>module.exports : { color: "red" | "blue"; }
>module : { "tests/cases/compiler/input": { color: "red" | "blue"; }; }
>exports : { color: "red" | "blue"; }

View File

@@ -9,7 +9,7 @@ var npm = module.exports = function (tree) {
module.exports.asReadInstalled = function (tree) {
>module.exports.asReadInstalled : Symbol(asReadInstalled, Decl(npm.js, 1, 1))
>module.exports : Symbol(asReadInstalled, Decl(npm.js, 1, 1))
>module : Symbol(module, Decl(npm.js, 0, 9))
>module : Symbol(module, Decl(npm.js, 0, 9), Decl(npm.js, 3, 13))
>exports : Symbol("tests/cases/conformance/salsa/npm", Decl(npm.js, 0, 0))
>asReadInstalled : Symbol(asReadInstalled, Decl(npm.js, 1, 1))
>tree : Symbol(tree, Decl(npm.js, 2, 43))
@@ -20,7 +20,7 @@ module.exports.asReadInstalled = function (tree) {
module.exports(tree)
>module.exports : Symbol("tests/cases/conformance/salsa/npm", Decl(npm.js, 0, 0))
>module : Symbol(module, Decl(npm.js, 3, 13))
>module : Symbol(module, Decl(npm.js, 0, 9), Decl(npm.js, 3, 13))
>exports : Symbol("tests/cases/conformance/salsa/npm", Decl(npm.js, 0, 0))
>tree : Symbol(tree, Decl(npm.js, 2, 43))
}

View File

@@ -0,0 +1,14 @@
interface MyComponentProps {
color: "red" | "blue"
}
interface StatelessComponent<P> {
(): any;
defaultProps?: Partial<P>;
}
const MyComponent: StatelessComponent<MyComponentProps> = () => null as any;
MyComponent.defaultProps = {
color: "red"
};

View File

@@ -0,0 +1,57 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename: input.js
/** @typedef {{ color: "red" | "blue" }} MyComponentProps */
/**
* @template P
* @typedef {{ (): any; defaultProps?: Partial<P> }} StatelessComponent */
/**
* @type {StatelessComponent<MyComponentProps>}
*/
const MyComponent = () => /* @type {any} */(null);
MyComponent.defaultProps = {
color: "red"
};
const MyComponent2 = () => null;
/**
* @type {MyComponentProps}
*/
MyComponent2.defaultProps = {
color: "red"
}
/**
* @type {StatelessComponent<MyComponentProps>}
*/
const check = MyComponent2;
/**
*
* @param {{ props: MyComponentProps }} p
*/
function expectLiteral(p) {}
function foo() {
/**
* @type {MyComponentProps}
*/
this.props = { color: "red" };
expectLiteral(this);
}
/**
* @type {MyComponentProps}
*/
module.exports = {
color: "red"
}
expectLiteral({ props: module.exports });