mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-12 01:48:33 -05:00
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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
22
tests/baselines/reference/expandoFunctionContextualTypes.js
Normal file
22
tests/baselines/reference/expandoFunctionContextualTypes.js
Normal 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"
|
||||
};
|
||||
@@ -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))
|
||||
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
};
|
||||
|
||||
@@ -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))
|
||||
|
||||
109
tests/baselines/reference/expandoFunctionContextualTypesJs.types
Normal file
109
tests/baselines/reference/expandoFunctionContextualTypesJs.types
Normal 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"; }
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
14
tests/cases/compiler/expandoFunctionContextualTypes.ts
Normal file
14
tests/cases/compiler/expandoFunctionContextualTypes.ts
Normal 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"
|
||||
};
|
||||
57
tests/cases/compiler/expandoFunctionContextualTypesJs.ts
Normal file
57
tests/cases/compiler/expandoFunctionContextualTypesJs.ts
Normal 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 });
|
||||
Reference in New Issue
Block a user