From 8cd9d9da95694da480421ec713af7f910b13e85d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 23 Jan 2018 14:32:46 -0800 Subject: [PATCH 1/7] Fix JSX contextual types to not eagerly become apparent --- src/compiler/checker.ts | 253 ++++++++++++------ ...xGenericTagHasCorrectInferences.errors.txt | 27 ++ .../checkJsxGenericTagHasCorrectInferences.js | 23 ++ ...kJsxGenericTagHasCorrectInferences.symbols | 74 +++++ ...eckJsxGenericTagHasCorrectInferences.types | 91 +++++++ ...checkJsxGenericTagHasCorrectInferences.tsx | 18 ++ 6 files changed, 401 insertions(+), 85 deletions(-) create mode 100644 tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.errors.txt create mode 100644 tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.js create mode 100644 tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.symbols create mode 100644 tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.types create mode 100644 tests/cases/conformance/jsx/checkJsxGenericTagHasCorrectInferences.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 01b3a047d1d91..382acc2f0ab00 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13898,56 +13898,35 @@ namespace ts { return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined; } - function getContextualTypeForJsxExpression(node: JsxExpression): Type { - // JSX expression can appear in two position : JSX Element's children or JSX attribute - const jsxAttributes = isJsxAttributeLike(node.parent) ? - node.parent.parent : - isJsxElement(node.parent) ? - node.parent.openingElement.attributes : - undefined; // node.parent is JsxFragment with no attributes - - if (!jsxAttributes) { - return undefined; // don't check children of a fragment - } - - // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type - // which is a type of the parameter of the signature we are trying out. - // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName - const attributesType = getContextualType(jsxAttributes); - - if (!attributesType || isTypeAny(attributesType)) { - return undefined; - } + function getContextualTypeForChildJsxExpression(node: JsxElement) { + const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName); + // JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty) + const jsxChildrenPropertyName = getJsxElementChildrenPropertyname(); + return attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "" ? getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName) : undefined; + } - if (isJsxAttribute(node.parent)) { - // JSX expression is in JSX attribute - return getTypeOfPropertyOfContextualType(attributesType, node.parent.name.escapedText); - } - else if (node.parent.kind === SyntaxKind.JsxElement) { - // JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty) - const jsxChildrenPropertyName = getJsxElementChildrenPropertyname(); - return jsxChildrenPropertyName && jsxChildrenPropertyName !== "" ? getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName) : anyType; - } - else { - // JSX expression is in JSX spread attribute - return attributesType; - } + function getContextualTypeForJsxExpression(node: JsxExpression): Type { + const exprParent = node.parent; + return isJsxAttributeLike(exprParent) + ? getContextualType(node) + : isJsxElement(exprParent) + ? getContextualTypeForChildJsxExpression(exprParent) + : undefined; } function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute) { // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type // which is a type of the parameter of the signature we are trying out. // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName - const attributesType = getContextualType(attribute.parent); - if (isJsxAttribute(attribute)) { + const attributesType = getApparentTypeOfContextualType(attribute.parent); if (!attributesType || isTypeAny(attributesType)) { return undefined; } return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); } else { - return attributesType; + return getContextualType(attribute.parent); } } @@ -14055,7 +14034,7 @@ namespace ts { return getContextualTypeForJsxAttribute(parent); case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: - return getAttributesTypeFromJsxOpeningLikeElement(parent); + return getContextualJsxElementAttributesType(parent); } return undefined; } @@ -14065,6 +14044,119 @@ namespace ts { return node ? node.contextualMapper : identityMapper; } + function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement) { + if (isJsxIntrinsicIdentifier(node.tagName)) { + return getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + } + const valueType = checkExpression(node.tagName); + if (isTypeAny(valueType)) { + // Short-circuit if the class tag is using an element type 'any' + return anyType; + } + + return mapType(valueType, signturesToParameterTypes); + + function signturesToParameterTypes(valueType: Type) { + // If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type + if (valueType.flags & TypeFlags.String) { + return anyType; + } + else if (valueType.flags & TypeFlags.StringLiteral) { + // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type + // For example: + // var CustomTag: "h1" = "h1"; + // Hello World + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements); + if (intrinsicElementsType !== unknownType) { + const stringLiteralTypeName = (valueType).value; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); + if (intrinsicProp) { + return getTypeOfSymbol(intrinsicProp); + } + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); + if (indexSignatureType) { + return indexSignatureType; + } + } + return anyType; + } + + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(valueType, SignatureKind.Construct); + let ctor = true; + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(valueType, SignatureKind.Call); + ctor = false; + if (signatures.length === 0) { + // We found no signatures at all, which is an error + return unknownType; + } + } + + return getUnionType(map(signatures, ctor ? getJsxPropsTypeFromConstructSignature : getJsxPropsTypeFromCallSignature), UnionReduction.None); + } + } + + function getJsxPropsTypeFromCallSignature(sig: Signature) { + let propsType = getTypeOfFirstParameterOfSignature(sig); + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); + if (intrinsicAttribs !== unknownType) { + propsType = intersectTypes(intrinsicAttribs, propsType); + } + return propsType; + } + + function getJsxPropsTypeFromConstructSignature(sig: Signature) { + let propsType: Type; + const hostClassType = getReturnTypeOfSignature(sig); + if (hostClassType) { + const propsName = getJsxElementPropertiesName(); + if (propsName === undefined) { + // There is no type ElementAttributesProperty, return 'any' + return anyType; + } + else if (propsName === "") { + // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead + return hostClassType; + } + else { + const attributesType = getTypeOfPropertyOfType(hostClassType, propsName); + if (!attributesType) { + // There is no property named 'props' on this instance type + return emptyObjectType; + } + else if (isTypeAny(attributesType) || (attributesType === unknownType)) { + // Props is of type 'any' or unknown + return attributesType; + } + else { + propsType = attributesType; + const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes); + if (intrinsicClassAttribs !== unknownType) { + const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); + if (typeParams) { + if (typeParams.length === 1) { + propsType = intersectTypes(createTypeReference(intrinsicClassAttribs, [hostClassType]), propsType); + } + } + else { + propsType = intersectTypes(propsType, intrinsicClassAttribs); + } + } + } + } + } + else { + propsType = getTypeOfFirstParameterOfSignature(sig); + } + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); + if (intrinsicAttribs !== unknownType) { + propsType = intersectTypes(intrinsicAttribs, propsType); + } + return propsType; + } + // If the given type is an object or union type with a single signature, and if that signature has at // least as many parameters as the given function, return the signature. Otherwise return undefined. function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature { @@ -14757,7 +14849,7 @@ namespace ts { * element is not a class element, or the class element type cannot be determined, returns 'undefined'. * For example, in the element , the element instance type is `MyClass` (not `typeof MyClass`). */ - function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type, sourceAttributesType: Type | undefined) { + function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) { Debug.assert(!(valueType.flags & TypeFlags.Union)); if (isTypeAny(valueType)) { // Short-circuit if the class tag is using an element type 'any' @@ -14776,27 +14868,21 @@ namespace ts { } } - if (sourceAttributesType) { - // Instantiate in context of source type - const instantiatedSignatures = []; - for (const signature of signatures) { - if (signature.typeParameters) { - const isJavascript = isInJavaScriptFile(node); - const inferenceContext = createInferenceContext(signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : 0); - const typeArguments = inferJsxTypeArguments(signature, sourceAttributesType, inferenceContext); - instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript)); - } - else { - instantiatedSignatures.push(signature); - } + // Instantiate in context of source type + const instantiatedSignatures = []; + for (const signature of signatures) { + if (signature.typeParameters) { + const isJavascript = isInJavaScriptFile(node); + const inferenceContext = createInferenceContext(signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : 0); + const typeArguments = inferJsxTypeArguments(signature, node, inferenceContext); + instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript)); + } + else { + instantiatedSignatures.push(signature); } - - return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype); - } - else { - // Do not instantiate if no source type is provided - type parameters and their constraints will be used by contextual typing - return getUnionType(map(signatures, getReturnTypeOfSignature), UnionReduction.Subtype); } + + return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype); } /** @@ -14982,14 +15068,13 @@ namespace ts { */ function resolveCustomJsxElementAttributesType(openingLikeElement: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean, - sourceAttributesType: Type | undefined, elementType: Type, elementClassType?: Type): Type { if (elementType.flags & TypeFlags.Union) { const types = (elementType as UnionType).types; return getUnionType(types.map(type => { - return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, sourceAttributesType, type, elementClassType); + return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, type, elementClassType); }), UnionReduction.Subtype); } @@ -15020,7 +15105,7 @@ namespace ts { } // Get the element instance type (the result of newing or invoking this tag) - const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType, sourceAttributesType); + const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType); // If we should include all stateless attributes type, then get all attributes type from all stateless function signature. // Otherwise get only attributes type from the signature picked by choose-overload logic. @@ -15033,7 +15118,7 @@ namespace ts { } // Issue an error if this return type isn't assignable to JSX.ElementClass - if (elementClassType && sourceAttributesType) { + if (elementClassType) { checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } @@ -15116,20 +15201,8 @@ namespace ts { * @param node a custom JSX opening-like element * @param shouldIncludeAllStatelessAttributesType a boolean value used by language service to get all possible attributes type from an overload stateless function component */ - function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, sourceAttributesType: Type, shouldIncludeAllStatelessAttributesType: boolean): Type { - if (!sourceAttributesType) { - // This ensures we cache non-inference uses of this calculation (ie, contextual types or services) - const links = getNodeLinks(node); - const linkLocation = shouldIncludeAllStatelessAttributesType ? "resolvedJsxElementAllAttributesType" : "resolvedJsxElementAttributesType"; - if (!links[linkLocation]) { - const elemClassType = getJsxGlobalElementClassType(); - return links[linkLocation] = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, sourceAttributesType, checkExpression(node.tagName), elemClassType); - } - return links[linkLocation]; - } - else { - return resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, sourceAttributesType, checkExpression(node.tagName), getJsxGlobalElementClassType()); - } + function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean): Type { + return resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, checkExpression(node.tagName), getJsxGlobalElementClassType()); } /** @@ -15144,7 +15217,7 @@ namespace ts { else { // Because in language service, the given JSX opening-like element may be incomplete and therefore, // we can't resolve to exact signature if the element is a stateless function component so the best thing to do is return all attributes type from all overloads. - return getCustomJsxElementAttributesType(node, /*sourceAttributesType*/ undefined, /*shouldIncludeAllStatelessAttributesType*/ true); + return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ true); } } @@ -15158,7 +15231,7 @@ namespace ts { return getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); } else { - return getCustomJsxElementAttributesType(node, /*sourceAttributesType*/ undefined, /*shouldIncludeAllStatelessAttributesType*/ false); + return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ false); } } @@ -15298,16 +15371,16 @@ namespace ts { // 3. Check if the two are assignable to each other + // targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element. + const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ? + getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) : + getCustomJsxElementAttributesType(openingLikeElement, /*shouldIncludeAllStatelessAttributesType*/ false); + // sourceAttributesType is a type of an attributes properties. // i.e
// attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes". const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement, checkMode); - // targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element. - const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ? - getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) : - getCustomJsxElementAttributesType(openingLikeElement, sourceAttributesType, /*shouldIncludeAllStatelessAttributesType*/ false); - // If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type. // but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass. if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || getPropertiesOfType(sourceAttributesType).length > 0)) { @@ -16183,9 +16256,19 @@ namespace ts { return getSignatureInstantiation(signature, getInferredTypes(context), isInJavaScriptFile(contextualSignature.declaration)); } - function inferJsxTypeArguments(signature: Signature, sourceAttributesType: Type, context: InferenceContext): Type[] { - const paramType = getTypeAtPosition(signature, 0); - inferTypes(context.inferences, sourceAttributesType, paramType); + function inferJsxTypeArguments(signature: Signature, node: JsxOpeningLikeElement, context: InferenceContext): Type[] { + { + // Skip context sensitive pass + const paramType = getTypeAtPosition(signature, 0); + const checkAttrTypeSkipContextSensitive = checkExpressionWithContextualType(node.attributes, paramType, identityMapper); + inferTypes(context.inferences, checkAttrTypeSkipContextSensitive, paramType); + } + { + // Standard pass + const paramType = getTypeAtPosition(signature, 0); + const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context); + inferTypes(context.inferences, checkAttrType, paramType); + } return getInferredTypes(context); } diff --git a/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.errors.txt b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.errors.txt new file mode 100644 index 0000000000000..a8c89c6751f1d --- /dev/null +++ b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.errors.txt @@ -0,0 +1,27 @@ +tests/cases/conformance/jsx/file.tsx(13,27): error TS2322: Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes> & { initialValues: { x: string; }; nextValues: {}; } & BaseProps<{ x: string; }> & { children?: ReactNode; }'. + Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'BaseProps<{ x: string; }>'. + Types of property 'nextValues' are incompatible. + Type '(a: { x: string; }) => string' is not assignable to type '(cur: { x: string; }) => { x: string; }'. + Type 'string' is not assignable to type '{ x: string; }'. + + +==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== + import * as React from "react"; + interface BaseProps { + initialValues: T; + nextValues: (cur: T) => T; + } + declare class GenericComponent extends React.Component, {}> { + iv: Values; + } + + let a = a} />; // No error + let b = a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint) + let c = ({ x: a.x })} />; // No Error + let d = a.x} />; // Error - `string` is not assignable to `{x: string}` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes> & { initialValues: { x: string; }; nextValues: {}; } & BaseProps<{ x: string; }> & { children?: ReactNode; }'. +!!! error TS2322: Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'BaseProps<{ x: string; }>'. +!!! error TS2322: Types of property 'nextValues' are incompatible. +!!! error TS2322: Type '(a: { x: string; }) => string' is not assignable to type '(cur: { x: string; }) => { x: string; }'. +!!! error TS2322: Type 'string' is not assignable to type '{ x: string; }'. \ No newline at end of file diff --git a/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.js b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.js new file mode 100644 index 0000000000000..fe0e910f7cbab --- /dev/null +++ b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.js @@ -0,0 +1,23 @@ +//// [file.tsx] +import * as React from "react"; +interface BaseProps { + initialValues: T; + nextValues: (cur: T) => T; +} +declare class GenericComponent extends React.Component, {}> { + iv: Values; +} + +let a = a} />; // No error +let b = a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint) +let c = ({ x: a.x })} />; // No Error +let d = a.x} />; // Error - `string` is not assignable to `{x: string}` + +//// [file.jsx] +"use strict"; +exports.__esModule = true; +var React = require("react"); +var a = ; // No error +var b = ; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint) +var c = ; // No Error +var d = ; // Error - `string` is not assignable to `{x: string}` diff --git a/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.symbols b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.symbols new file mode 100644 index 0000000000000..314dc8a5384b0 --- /dev/null +++ b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.symbols @@ -0,0 +1,74 @@ +=== tests/cases/conformance/jsx/file.tsx === +import * as React from "react"; +>React : Symbol(React, Decl(file.tsx, 0, 6)) + +interface BaseProps { +>BaseProps : Symbol(BaseProps, Decl(file.tsx, 0, 31)) +>T : Symbol(T, Decl(file.tsx, 1, 20)) + + initialValues: T; +>initialValues : Symbol(BaseProps.initialValues, Decl(file.tsx, 1, 24)) +>T : Symbol(T, Decl(file.tsx, 1, 20)) + + nextValues: (cur: T) => T; +>nextValues : Symbol(BaseProps.nextValues, Decl(file.tsx, 2, 19)) +>cur : Symbol(cur, Decl(file.tsx, 3, 15)) +>T : Symbol(T, Decl(file.tsx, 1, 20)) +>T : Symbol(T, Decl(file.tsx, 1, 20)) +} +declare class GenericComponent extends React.Component, {}> { +>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1)) +>Props : Symbol(Props, Decl(file.tsx, 5, 31)) +>Values : Symbol(Values, Decl(file.tsx, 5, 42)) +>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55), Decl(react.d.ts, 161, 66)) +>React : Symbol(React, Decl(file.tsx, 0, 6)) +>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55), Decl(react.d.ts, 161, 66)) +>Props : Symbol(Props, Decl(file.tsx, 5, 31)) +>BaseProps : Symbol(BaseProps, Decl(file.tsx, 0, 31)) +>Values : Symbol(Values, Decl(file.tsx, 5, 42)) + + iv: Values; +>iv : Symbol(GenericComponent.iv, Decl(file.tsx, 5, 116)) +>Values : Symbol(Values, Decl(file.tsx, 5, 42)) +} + +let a = a} />; // No error +>a : Symbol(a, Decl(file.tsx, 9, 3)) +>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1)) +>initialValues : Symbol(initialValues, Decl(file.tsx, 9, 25)) +>x : Symbol(x, Decl(file.tsx, 9, 42)) +>nextValues : Symbol(nextValues, Decl(file.tsx, 9, 52)) +>a : Symbol(a, Decl(file.tsx, 9, 65)) +>a : Symbol(a, Decl(file.tsx, 9, 65)) + +let b = a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint) +>b : Symbol(b, Decl(file.tsx, 10, 3)) +>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1)) +>initialValues : Symbol(initialValues, Decl(file.tsx, 10, 25)) +>nextValues : Symbol(nextValues, Decl(file.tsx, 10, 44)) +>a : Symbol(a, Decl(file.tsx, 10, 57)) +>a : Symbol(a, Decl(file.tsx, 10, 57)) + +let c = ({ x: a.x })} />; // No Error +>c : Symbol(c, Decl(file.tsx, 11, 3)) +>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1)) +>initialValues : Symbol(initialValues, Decl(file.tsx, 11, 25)) +>x : Symbol(x, Decl(file.tsx, 11, 42)) +>nextValues : Symbol(nextValues, Decl(file.tsx, 11, 52)) +>a : Symbol(a, Decl(file.tsx, 11, 65)) +>x : Symbol(x, Decl(file.tsx, 11, 72)) +>a.x : Symbol(x, Decl(file.tsx, 11, 42)) +>a : Symbol(a, Decl(file.tsx, 11, 65)) +>x : Symbol(x, Decl(file.tsx, 11, 42)) + +let d = a.x} />; // Error - `string` is not assignable to `{x: string}` +>d : Symbol(d, Decl(file.tsx, 12, 3)) +>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1)) +>initialValues : Symbol(initialValues, Decl(file.tsx, 12, 25)) +>x : Symbol(x, Decl(file.tsx, 12, 42)) +>nextValues : Symbol(nextValues, Decl(file.tsx, 12, 52)) +>a : Symbol(a, Decl(file.tsx, 12, 65)) +>a.x : Symbol(x, Decl(file.tsx, 12, 42)) +>a : Symbol(a, Decl(file.tsx, 12, 65)) +>x : Symbol(x, Decl(file.tsx, 12, 42)) + diff --git a/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.types b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.types new file mode 100644 index 0000000000000..1ced851625380 --- /dev/null +++ b/tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.types @@ -0,0 +1,91 @@ +=== tests/cases/conformance/jsx/file.tsx === +import * as React from "react"; +>React : typeof React + +interface BaseProps { +>BaseProps : BaseProps +>T : T + + initialValues: T; +>initialValues : T +>T : T + + nextValues: (cur: T) => T; +>nextValues : (cur: T) => T +>cur : T +>T : T +>T : T +} +declare class GenericComponent extends React.Component, {}> { +>GenericComponent : GenericComponent +>Props : Props +>Values : Values +>React.Component : React.Component, {}> +>React : typeof React +>Component : typeof React.Component +>Props : Props +>BaseProps : BaseProps +>Values : Values + + iv: Values; +>iv : Values +>Values : Values +} + +let a = a} />; // No error +>a : JSX.Element +> a} /> : JSX.Element +>GenericComponent : typeof GenericComponent +>initialValues : { x: string; } +>{ x: "y" } : { x: string; } +>x : string +>"y" : "y" +>nextValues : (a: { x: string; }) => { x: string; } +>a => a : (a: { x: string; }) => { x: string; } +>a : { x: string; } +>a : { x: string; } + +let b = a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint) +>b : JSX.Element +> a} /> : JSX.Element +>GenericComponent : typeof GenericComponent +>initialValues : number +>12 : 12 +>nextValues : (a: number) => number +>a => a : (a: number) => number +>a : number +>a : number + +let c = ({ x: a.x })} />; // No Error +>c : JSX.Element +> ({ x: a.x })} /> : JSX.Element +>GenericComponent : typeof GenericComponent +>initialValues : { x: string; } +>{ x: "y" } : { x: string; } +>x : string +>"y" : "y" +>nextValues : (a: { x: string; }) => { x: string; } +>a => ({ x: a.x }) : (a: { x: string; }) => { x: string; } +>a : { x: string; } +>({ x: a.x }) : { x: string; } +>{ x: a.x } : { x: string; } +>x : string +>a.x : string +>a : { x: string; } +>x : string + +let d = a.x} />; // Error - `string` is not assignable to `{x: string}` +>d : JSX.Element +> a.x} /> : JSX.Element +>GenericComponent : typeof GenericComponent +>initialValues : { x: string; } +>{ x: "y" } : { x: string; } +>x : string +>"y" : "y" +>nextValues : (a: { x: string; }) => string +>a => a.x : (a: { x: string; }) => string +>a : { x: string; } +>a.x : string +>a : { x: string; } +>x : string + diff --git a/tests/cases/conformance/jsx/checkJsxGenericTagHasCorrectInferences.tsx b/tests/cases/conformance/jsx/checkJsxGenericTagHasCorrectInferences.tsx new file mode 100644 index 0000000000000..04574d4e09810 --- /dev/null +++ b/tests/cases/conformance/jsx/checkJsxGenericTagHasCorrectInferences.tsx @@ -0,0 +1,18 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @skipLibCheck: true +// @libFiles: react.d.ts,lib.d.ts +import * as React from "react"; +interface BaseProps { + initialValues: T; + nextValues: (cur: T) => T; +} +declare class GenericComponent extends React.Component, {}> { + iv: Values; +} + +let a = a} />; // No error +let b = a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint) +let c = ({ x: a.x })} />; // No Error +let d = a.x} />; // Error - `string` is not assignable to `{x: string}` \ No newline at end of file From 0a08644621cf2c30694271761e6651a4dfb6d52c Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 24 Jan 2018 12:58:15 -0800 Subject: [PATCH 2/7] Apply changes from code review, unify common code --- src/compiler/checker.ts | 165 +++++++----------- src/compiler/types.ts | 1 + .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 4 files changed, 64 insertions(+), 104 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 382acc2f0ab00..a291f8b25460b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14054,9 +14054,9 @@ namespace ts { return anyType; } - return mapType(valueType, signturesToParameterTypes); + return mapType(valueType, signaturesToParameterTypes); - function signturesToParameterTypes(valueType: Type) { + function signaturesToParameterTypes(valueType: Type) { // If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type if (valueType.flags & TypeFlags.String) { return anyType; @@ -14107,54 +14107,61 @@ namespace ts { return propsType; } - function getJsxPropsTypeFromConstructSignature(sig: Signature) { - let propsType: Type; - const hostClassType = getReturnTypeOfSignature(sig); - if (hostClassType) { - const propsName = getJsxElementPropertiesName(); - if (propsName === undefined) { - // There is no type ElementAttributesProperty, return 'any' - return anyType; + function getJsxPropsTypeFromClassType(hostClassType: Type) { + if (isTypeAny(hostClassType)) { + return hostClassType; + } + + const propsName = getJsxElementPropertiesName(); + if (propsName === undefined) { + // There is no type ElementAttributesProperty, return 'any' + return anyType; + } + else if (propsName === "") { + // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead + return hostClassType; + } + else { + const attributesType = getTypeOfPropertyOfType(hostClassType, propsName); + + if (!attributesType) { + // There is no property named 'props' on this instance type + return emptyObjectType; } - else if (propsName === "") { - // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead - return hostClassType; + else if (isTypeAny(attributesType)) { + // Props is of type 'any' or unknown + return attributesType; } else { - const attributesType = getTypeOfPropertyOfType(hostClassType, propsName); - if (!attributesType) { - // There is no property named 'props' on this instance type - return emptyObjectType; - } - else if (isTypeAny(attributesType) || (attributesType === unknownType)) { - // Props is of type 'any' or unknown - return attributesType; + // Normal case -- add in IntrinsicClassElements and IntrinsicElements + let apparentAttributesType = attributesType; + const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes); + if (intrinsicClassAttribs !== unknownType) { + const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); + apparentAttributesType = intersectTypes( + length(typeParams) + ? createTypeReference(intrinsicClassAttribs, [hostClassType]) + : intrinsicClassAttribs, + apparentAttributesType + ); } - else { - propsType = attributesType; - const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes); - if (intrinsicClassAttribs !== unknownType) { - const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); - if (typeParams) { - if (typeParams.length === 1) { - propsType = intersectTypes(createTypeReference(intrinsicClassAttribs, [hostClassType]), propsType); - } - } - else { - propsType = intersectTypes(propsType, intrinsicClassAttribs); - } - } + + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); + if (intrinsicAttribs !== unknownType) { + apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); } + + return apparentAttributesType; } } - else { - propsType = getTypeOfFirstParameterOfSignature(sig); - } - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); - if (intrinsicAttribs !== unknownType) { - propsType = intersectTypes(intrinsicAttribs, propsType); + } + + function getJsxPropsTypeFromConstructSignature(sig: Signature) { + const hostClassType = getReturnTypeOfSignature(sig); + if (hostClassType) { + return getJsxPropsTypeFromClassType(hostClassType); } - return propsType; + return getJsxPropsTypeFromCallSignature(sig); } // If the given type is an object or union type with a single signature, and if that signature has at @@ -14873,7 +14880,7 @@ namespace ts { for (const signature of signatures) { if (signature.typeParameters) { const isJavascript = isInJavaScriptFile(node); - const inferenceContext = createInferenceContext(signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : 0); + const inferenceContext = createInferenceContext(signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : InferenceFlags.None); const typeArguments = inferJsxTypeArguments(signature, node, inferenceContext); instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript)); } @@ -15122,54 +15129,7 @@ namespace ts { checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } - if (isTypeAny(elemInstanceType)) { - return elemInstanceType; - } - - const propsName = getJsxElementPropertiesName(); - if (propsName === undefined) { - // There is no type ElementAttributesProperty, return 'any' - return anyType; - } - else if (propsName === "") { - // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead - return elemInstanceType; - } - else { - const attributesType = getTypeOfPropertyOfType(elemInstanceType, propsName); - - if (!attributesType) { - // There is no property named 'props' on this instance type - return emptyObjectType; - } - else if (isTypeAny(attributesType) || (attributesType === unknownType)) { - // Props is of type 'any' or unknown - return attributesType; - } - else { - // Normal case -- add in IntrinsicClassElements and IntrinsicElements - let apparentAttributesType = attributesType; - const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes); - if (intrinsicClassAttribs !== unknownType) { - const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); - if (typeParams) { - if (typeParams.length === 1) { - apparentAttributesType = intersectTypes(createTypeReference(intrinsicClassAttribs, [elemInstanceType]), apparentAttributesType); - } - } - else { - apparentAttributesType = intersectTypes(attributesType, intrinsicClassAttribs); - } - } - - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); - if (intrinsicAttribs !== unknownType) { - apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); - } - - return apparentAttributesType; - } - } + return getJsxPropsTypeFromClassType(elemInstanceType); } /** @@ -16257,18 +16217,15 @@ namespace ts { } function inferJsxTypeArguments(signature: Signature, node: JsxOpeningLikeElement, context: InferenceContext): Type[] { - { - // Skip context sensitive pass - const paramType = getTypeAtPosition(signature, 0); - const checkAttrTypeSkipContextSensitive = checkExpressionWithContextualType(node.attributes, paramType, identityMapper); - inferTypes(context.inferences, checkAttrTypeSkipContextSensitive, paramType); - } - { - // Standard pass - const paramType = getTypeAtPosition(signature, 0); - const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context); - inferTypes(context.inferences, checkAttrType, paramType); - } + // Skip context sensitive pass + const skipContextParamType = getTypeAtPosition(signature, 0); + const checkAttrTypeSkipContextSensitive = checkExpressionWithContextualType(node.attributes, skipContextParamType, identityMapper); + inferTypes(context.inferences, checkAttrTypeSkipContextSensitive, skipContextParamType); + + // Standard pass + const paramType = getTypeAtPosition(signature, 0); + const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context); + inferTypes(context.inferences, checkAttrType, paramType); return getInferredTypes(context); } @@ -17014,7 +16971,7 @@ namespace ts { let candidate: Signature; const inferenceContext = originalCandidate.typeParameters ? - createInferenceContext(originalCandidate, /*flags*/ isInJavaScriptFile(node) ? InferenceFlags.AnyDefault : 0) : + createInferenceContext(originalCandidate, /*flags*/ isInJavaScriptFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None) : undefined; while (true) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f6ca2f45d3401..324fc242748ad 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3846,6 +3846,7 @@ namespace ts { } export const enum InferenceFlags { + None = 0, // No special inference behaviors InferUnionTypes = 1 << 0, // Infer union types for disjoint candidates (otherwise unknownType) NoDefault = 1 << 1, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType) AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index cd1b577b71173..93599356dda64 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2192,6 +2192,7 @@ declare namespace ts { isFixed: boolean; } enum InferenceFlags { + None = 0, InferUnionTypes = 1, NoDefault = 2, AnyDefault = 4, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3cbca96f8fa97..e7d3093e224fd 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2192,6 +2192,7 @@ declare namespace ts { isFixed: boolean; } enum InferenceFlags { + None = 0, InferUnionTypes = 1, NoDefault = 2, AnyDefault = 4, From 7c63c49d2c730458be23ffbca1ae46bbaae1a39e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 2 Feb 2018 17:19:27 -0800 Subject: [PATCH 3/7] Fix jsx children contextual typing --- src/compiler/checker.ts | 22 ++- ...xChildrenGenericContextualTypes.errors.txt | 68 ++++++++ .../jsxChildrenGenericContextualTypes.js | 38 ++++ .../jsxChildrenGenericContextualTypes.symbols | 119 +++++++++++++ .../jsxChildrenGenericContextualTypes.types | 165 ++++++++++++++++++ .../jsxChildrenGenericContextualTypes.tsx | 24 +++ 6 files changed, 429 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/jsxChildrenGenericContextualTypes.errors.txt create mode 100644 tests/baselines/reference/jsxChildrenGenericContextualTypes.js create mode 100644 tests/baselines/reference/jsxChildrenGenericContextualTypes.symbols create mode 100644 tests/baselines/reference/jsxChildrenGenericContextualTypes.types create mode 100644 tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a291f8b25460b..32ac08400cc34 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14788,7 +14788,7 @@ namespace ts { } } else { - childrenTypes.push(checkExpression(child, checkMode)); + childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); } } return childrenTypes; @@ -18957,16 +18957,24 @@ namespace ts { return stringType; } + function getContextNode(node: Expression): Node { + if (node.kind === SyntaxKind.JsxAttributes) { + return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) + } + return node; + } + function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper: TypeMapper | undefined): Type { - const saveContextualType = node.contextualType; - const saveContextualMapper = node.contextualMapper; - node.contextualType = contextualType; - node.contextualMapper = contextualMapper; + const context = getContextNode(node); + const saveContextualType = context.contextualType; + const saveContextualMapper = context.contextualMapper; + context.contextualType = contextualType; + context.contextualMapper = contextualMapper; const checkMode = contextualMapper === identityMapper ? CheckMode.SkipContextSensitive : contextualMapper ? CheckMode.Inferential : CheckMode.Contextual; const result = checkExpression(node, checkMode); - node.contextualType = saveContextualType; - node.contextualMapper = saveContextualMapper; + context.contextualType = saveContextualType; + context.contextualMapper = saveContextualMapper; return result; } diff --git a/tests/baselines/reference/jsxChildrenGenericContextualTypes.errors.txt b/tests/baselines/reference/jsxChildrenGenericContextualTypes.errors.txt new file mode 100644 index 0000000000000..9f5778c863881 --- /dev/null +++ b/tests/baselines/reference/jsxChildrenGenericContextualTypes.errors.txt @@ -0,0 +1,68 @@ +tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx(20,22): error TS2322: Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'. + Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'LitProps<"x">'. + Types of property 'children' are incompatible. + Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x">) => "x"'. + Type '"y"' is not assignable to type '"x"'. +tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx(21,27): error TS2322: Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x" | "y">'. + Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'LitProps<"x" | "y">'. + Types of property 'children' are incompatible. + Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x" | "y">) => "x" | "y"'. + Types of parameters 'p' and 'x' are incompatible. + Type 'LitProps<"x" | "y">' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'. + Type 'LitProps<"x" | "y">' is not assignable to type 'LitProps<"x">'. + Types of property 'prop' are incompatible. + Type '"x" | "y"' is not assignable to type '"x"'. + Type '"y"' is not assignable to type '"x"'. +tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx(22,29): error TS2322: Type '{ children: () => number; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'. + Type '{ children: () => number; prop: "x"; }' is not assignable to type 'LitProps<"x">'. + Types of property 'children' are incompatible. + Type '() => number' is not assignable to type '(x: LitProps<"x">) => "x"'. + Type 'number' is not assignable to type '"x"'. + + +==== tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx (3 errors) ==== + namespace JSX { + export interface Element {} + export interface ElementAttributesProperty { props: {}; } + export interface ElementChildrenAttribute { children: {}; } + export interface IntrinsicAttributes {} + export interface IntrinsicElements { [key: string]: Element } + } + const Elem = (p: { prop: T, children: (t: T) => T }) =>
; + Elem({prop: {a: "x"}, children: i => ({a: "z"})}); + const q = ({a: "z"})} /> + const qq = {i => ({a: "z"})} + + interface LitProps { prop: T, children: (x: this) => T } + const ElemLit = (p: LitProps) =>
; + ElemLit({prop: "x", children: () => "x"}); + const j = "x"} /> + const jj = {() => "x"} + + // Should error + const arg = "y"} /> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'. +!!! error TS2322: Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'LitProps<"x">'. +!!! error TS2322: Types of property 'children' are incompatible. +!!! error TS2322: Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x">) => "x"'. +!!! error TS2322: Type '"y"' is not assignable to type '"x"'. + const argchild = {p => "y"} + ~~~~~~~~ +!!! error TS2322: Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x" | "y">'. +!!! error TS2322: Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'LitProps<"x" | "y">'. +!!! error TS2322: Types of property 'children' are incompatible. +!!! error TS2322: Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x" | "y">) => "x" | "y"'. +!!! error TS2322: Types of parameters 'p' and 'x' are incompatible. +!!! error TS2322: Type 'LitProps<"x" | "y">' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'. +!!! error TS2322: Type 'LitProps<"x" | "y">' is not assignable to type 'LitProps<"x">'. +!!! error TS2322: Types of property 'prop' are incompatible. +!!! error TS2322: Type '"x" | "y"' is not assignable to type '"x"'. +!!! error TS2322: Type '"y"' is not assignable to type '"x"'. + const mismatched = {() => 12} + ~~~~~~~~ +!!! error TS2322: Type '{ children: () => number; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'. +!!! error TS2322: Type '{ children: () => number; prop: "x"; }' is not assignable to type 'LitProps<"x">'. +!!! error TS2322: Types of property 'children' are incompatible. +!!! error TS2322: Type '() => number' is not assignable to type '(x: LitProps<"x">) => "x"'. +!!! error TS2322: Type 'number' is not assignable to type '"x"'. \ No newline at end of file diff --git a/tests/baselines/reference/jsxChildrenGenericContextualTypes.js b/tests/baselines/reference/jsxChildrenGenericContextualTypes.js new file mode 100644 index 0000000000000..cffa13be41212 --- /dev/null +++ b/tests/baselines/reference/jsxChildrenGenericContextualTypes.js @@ -0,0 +1,38 @@ +//// [jsxChildrenGenericContextualTypes.tsx] +namespace JSX { + export interface Element {} + export interface ElementAttributesProperty { props: {}; } + export interface ElementChildrenAttribute { children: {}; } + export interface IntrinsicAttributes {} + export interface IntrinsicElements { [key: string]: Element } +} +const Elem = (p: { prop: T, children: (t: T) => T }) =>
; +Elem({prop: {a: "x"}, children: i => ({a: "z"})}); +const q = ({a: "z"})} /> +const qq = {i => ({a: "z"})} + +interface LitProps { prop: T, children: (x: this) => T } +const ElemLit = (p: LitProps) =>
; +ElemLit({prop: "x", children: () => "x"}); +const j = "x"} /> +const jj = {() => "x"} + +// Should error +const arg = "y"} /> +const argchild = {p => "y"} +const mismatched = {() => 12} + +//// [jsxChildrenGenericContextualTypes.jsx] +"use strict"; +var Elem = function (p) { return
; }; +Elem({ prop: { a: "x" }, children: function (i) { return ({ a: "z" }); } }); +var q = ; +var qq = {function (i) { return ({ a: "z" }); }}; +var ElemLit = function (p) { return
; }; +ElemLit({ prop: "x", children: function () { return "x"; } }); +var j = ; +var jj = {function () { return "x"; }}; +// Should error +var arg = ; +var argchild = {function (p) { return "y"; }}; +var mismatched = {function () { return 12; }}; diff --git a/tests/baselines/reference/jsxChildrenGenericContextualTypes.symbols b/tests/baselines/reference/jsxChildrenGenericContextualTypes.symbols new file mode 100644 index 0000000000000..ce5535615eac1 --- /dev/null +++ b/tests/baselines/reference/jsxChildrenGenericContextualTypes.symbols @@ -0,0 +1,119 @@ +=== tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx === +namespace JSX { +>JSX : Symbol(JSX, Decl(jsxChildrenGenericContextualTypes.tsx, 0, 0)) + + export interface Element {} +>Element : Symbol(Element, Decl(jsxChildrenGenericContextualTypes.tsx, 0, 15)) + + export interface ElementAttributesProperty { props: {}; } +>ElementAttributesProperty : Symbol(ElementAttributesProperty, Decl(jsxChildrenGenericContextualTypes.tsx, 1, 31)) +>props : Symbol(ElementAttributesProperty.props, Decl(jsxChildrenGenericContextualTypes.tsx, 2, 48)) + + export interface ElementChildrenAttribute { children: {}; } +>ElementChildrenAttribute : Symbol(ElementChildrenAttribute, Decl(jsxChildrenGenericContextualTypes.tsx, 2, 61)) +>children : Symbol(ElementChildrenAttribute.children, Decl(jsxChildrenGenericContextualTypes.tsx, 3, 47)) + + export interface IntrinsicAttributes {} +>IntrinsicAttributes : Symbol(IntrinsicAttributes, Decl(jsxChildrenGenericContextualTypes.tsx, 3, 63)) + + export interface IntrinsicElements { [key: string]: Element } +>IntrinsicElements : Symbol(IntrinsicElements, Decl(jsxChildrenGenericContextualTypes.tsx, 4, 43)) +>key : Symbol(key, Decl(jsxChildrenGenericContextualTypes.tsx, 5, 42)) +>Element : Symbol(Element, Decl(jsxChildrenGenericContextualTypes.tsx, 0, 15)) +} +const Elem = (p: { prop: T, children: (t: T) => T }) =>
; +>Elem : Symbol(Elem, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 5)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 14)) +>U : Symbol(U, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 16)) +>p : Symbol(p, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 25)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 29)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 14)) +>children : Symbol(children, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 38)) +>t : Symbol(t, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 50)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 14)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 14)) +>div : Symbol(JSX.IntrinsicElements, Decl(jsxChildrenGenericContextualTypes.tsx, 4, 43)) +>div : Symbol(JSX.IntrinsicElements, Decl(jsxChildrenGenericContextualTypes.tsx, 4, 43)) + +Elem({prop: {a: "x"}, children: i => ({a: "z"})}); +>Elem : Symbol(Elem, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 8, 6)) +>a : Symbol(a, Decl(jsxChildrenGenericContextualTypes.tsx, 8, 13)) +>children : Symbol(children, Decl(jsxChildrenGenericContextualTypes.tsx, 8, 21)) +>i : Symbol(i, Decl(jsxChildrenGenericContextualTypes.tsx, 8, 31)) +>a : Symbol(a, Decl(jsxChildrenGenericContextualTypes.tsx, 8, 39)) + +const q = ({a: "z"})} /> +>q : Symbol(q, Decl(jsxChildrenGenericContextualTypes.tsx, 9, 5)) +>Elem : Symbol(Elem, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 9, 15)) +>a : Symbol(a, Decl(jsxChildrenGenericContextualTypes.tsx, 9, 23)) +>children : Symbol(children, Decl(jsxChildrenGenericContextualTypes.tsx, 9, 31)) +>i : Symbol(i, Decl(jsxChildrenGenericContextualTypes.tsx, 9, 42)) +>a : Symbol(a, Decl(jsxChildrenGenericContextualTypes.tsx, 9, 49)) + +const qq = {i => ({a: "z"})} +>qq : Symbol(qq, Decl(jsxChildrenGenericContextualTypes.tsx, 10, 5)) +>Elem : Symbol(Elem, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 10, 16)) +>a : Symbol(a, Decl(jsxChildrenGenericContextualTypes.tsx, 10, 24)) +>i : Symbol(i, Decl(jsxChildrenGenericContextualTypes.tsx, 10, 34)) +>a : Symbol(a, Decl(jsxChildrenGenericContextualTypes.tsx, 10, 41)) +>Elem : Symbol(Elem, Decl(jsxChildrenGenericContextualTypes.tsx, 7, 5)) + +interface LitProps { prop: T, children: (x: this) => T } +>LitProps : Symbol(LitProps, Decl(jsxChildrenGenericContextualTypes.tsx, 10, 57)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 12, 19)) +>prop : Symbol(LitProps.prop, Decl(jsxChildrenGenericContextualTypes.tsx, 12, 23)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 12, 19)) +>children : Symbol(LitProps.children, Decl(jsxChildrenGenericContextualTypes.tsx, 12, 32)) +>x : Symbol(x, Decl(jsxChildrenGenericContextualTypes.tsx, 12, 44)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 12, 19)) + +const ElemLit = (p: LitProps) =>
; +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 17)) +>p : Symbol(p, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 35)) +>LitProps : Symbol(LitProps, Decl(jsxChildrenGenericContextualTypes.tsx, 10, 57)) +>T : Symbol(T, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 17)) +>div : Symbol(JSX.IntrinsicElements, Decl(jsxChildrenGenericContextualTypes.tsx, 4, 43)) +>div : Symbol(JSX.IntrinsicElements, Decl(jsxChildrenGenericContextualTypes.tsx, 4, 43)) + +ElemLit({prop: "x", children: () => "x"}); +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 14, 9)) +>children : Symbol(children, Decl(jsxChildrenGenericContextualTypes.tsx, 14, 19)) + +const j = "x"} /> +>j : Symbol(j, Decl(jsxChildrenGenericContextualTypes.tsx, 15, 5)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 15, 18)) +>children : Symbol(children, Decl(jsxChildrenGenericContextualTypes.tsx, 15, 27)) + +const jj = {() => "x"} +>jj : Symbol(jj, Decl(jsxChildrenGenericContextualTypes.tsx, 16, 5)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 16, 19)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) + +// Should error +const arg = "y"} /> +>arg : Symbol(arg, Decl(jsxChildrenGenericContextualTypes.tsx, 19, 5)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 19, 20)) +>children : Symbol(children, Decl(jsxChildrenGenericContextualTypes.tsx, 19, 29)) +>p : Symbol(p, Decl(jsxChildrenGenericContextualTypes.tsx, 19, 40)) + +const argchild = {p => "y"} +>argchild : Symbol(argchild, Decl(jsxChildrenGenericContextualTypes.tsx, 20, 5)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 20, 25)) +>p : Symbol(p, Decl(jsxChildrenGenericContextualTypes.tsx, 20, 36)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) + +const mismatched = {() => 12} +>mismatched : Symbol(mismatched, Decl(jsxChildrenGenericContextualTypes.tsx, 21, 5)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) +>prop : Symbol(prop, Decl(jsxChildrenGenericContextualTypes.tsx, 21, 27)) +>ElemLit : Symbol(ElemLit, Decl(jsxChildrenGenericContextualTypes.tsx, 13, 5)) + diff --git a/tests/baselines/reference/jsxChildrenGenericContextualTypes.types b/tests/baselines/reference/jsxChildrenGenericContextualTypes.types new file mode 100644 index 0000000000000..0196248d39c10 --- /dev/null +++ b/tests/baselines/reference/jsxChildrenGenericContextualTypes.types @@ -0,0 +1,165 @@ +=== tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx === +namespace JSX { +>JSX : any + + export interface Element {} +>Element : Element + + export interface ElementAttributesProperty { props: {}; } +>ElementAttributesProperty : ElementAttributesProperty +>props : {} + + export interface ElementChildrenAttribute { children: {}; } +>ElementChildrenAttribute : ElementChildrenAttribute +>children : {} + + export interface IntrinsicAttributes {} +>IntrinsicAttributes : IntrinsicAttributes + + export interface IntrinsicElements { [key: string]: Element } +>IntrinsicElements : IntrinsicElements +>key : string +>Element : Element +} +const Elem = (p: { prop: T, children: (t: T) => T }) =>
; +>Elem : (p: { prop: T; children: (t: T) => T; }) => JSX.Element +>(p: { prop: T, children: (t: T) => T }) =>
: (p: { prop: T; children: (t: T) => T; }) => JSX.Element +>T : T +>U : U +>p : { prop: T; children: (t: T) => T; } +>prop : T +>T : T +>children : (t: T) => T +>t : T +>T : T +>T : T +>
: JSX.Element +>div : any +>div : any + +Elem({prop: {a: "x"}, children: i => ({a: "z"})}); +>Elem({prop: {a: "x"}, children: i => ({a: "z"})}) : JSX.Element +>Elem : (p: { prop: T; children: (t: T) => T; }) => JSX.Element +>{prop: {a: "x"}, children: i => ({a: "z"})} : { prop: { a: string; }; children: (i: { a: string; }) => { a: string; }; } +>prop : { a: string; } +>{a: "x"} : { a: string; } +>a : string +>"x" : "x" +>children : (i: { a: string; }) => { a: string; } +>i => ({a: "z"}) : (i: { a: string; }) => { a: string; } +>i : { a: string; } +>({a: "z"}) : { a: string; } +>{a: "z"} : { a: string; } +>a : string +>"z" : "z" + +const q = ({a: "z"})} /> +>q : JSX.Element +> ({a: "z"})} /> : JSX.Element +>Elem : (p: { prop: T; children: (t: T) => T; }) => JSX.Element +>prop : { a: string; } +>{a: "x"} : { a: string; } +>a : string +>"x" : "x" +>children : (i: { a: string; }) => { a: string; } +>i => ({a: "z"}) : (i: { a: string; }) => { a: string; } +>i : { a: string; } +>({a: "z"}) : { a: string; } +>{a: "z"} : { a: string; } +>a : string +>"z" : "z" + +const qq = {i => ({a: "z"})} +>qq : JSX.Element +>{i => ({a: "z"})} : JSX.Element +>Elem : (p: { prop: T; children: (t: T) => T; }) => JSX.Element +>prop : { a: string; } +>{a: "x"} : { a: string; } +>a : string +>"x" : "x" +>i => ({a: "z"}) : (i: { a: string; }) => { a: string; } +>i : { a: string; } +>({a: "z"}) : { a: string; } +>{a: "z"} : { a: string; } +>a : string +>"z" : "z" +>Elem : (p: { prop: T; children: (t: T) => T; }) => JSX.Element + +interface LitProps { prop: T, children: (x: this) => T } +>LitProps : LitProps +>T : T +>prop : T +>T : T +>children : (x: this) => T +>x : this +>T : T + +const ElemLit = (p: LitProps) =>
; +>ElemLit : (p: LitProps) => JSX.Element +>(p: LitProps) =>
: (p: LitProps) => JSX.Element +>T : T +>p : LitProps +>LitProps : LitProps +>T : T +>
: JSX.Element +>div : any +>div : any + +ElemLit({prop: "x", children: () => "x"}); +>ElemLit({prop: "x", children: () => "x"}) : JSX.Element +>ElemLit : (p: LitProps) => JSX.Element +>{prop: "x", children: () => "x"} : { prop: "x"; children: () => "x"; } +>prop : "x" +>"x" : "x" +>children : () => "x" +>() => "x" : () => "x" +>"x" : "x" + +const j = "x"} /> +>j : JSX.Element +> "x"} /> : JSX.Element +>ElemLit : (p: LitProps) => JSX.Element +>prop : "x" +>children : () => "x" +>() => "x" : () => "x" +>"x" : "x" + +const jj = {() => "x"} +>jj : JSX.Element +>{() => "x"} : JSX.Element +>ElemLit : (p: LitProps) => JSX.Element +>prop : "x" +>() => "x" : () => "x" +>"x" : "x" +>ElemLit : (p: LitProps) => JSX.Element + +// Should error +const arg = "y"} /> +>arg : JSX.Element +> "y"} /> : JSX.Element +>ElemLit : (p: LitProps) => JSX.Element +>prop : "x" +>children : (p: JSX.IntrinsicAttributes & LitProps<"x">) => "y" +>p => "y" : (p: JSX.IntrinsicAttributes & LitProps<"x">) => "y" +>p : JSX.IntrinsicAttributes & LitProps<"x"> +>"y" : "y" + +const argchild = {p => "y"} +>argchild : JSX.Element +>{p => "y"} : JSX.Element +>ElemLit : (p: LitProps) => JSX.Element +>prop : "x" +>p => "y" : (p: JSX.IntrinsicAttributes & LitProps<"x">) => "y" +>p : JSX.IntrinsicAttributes & LitProps<"x"> +>"y" : "y" +>ElemLit : (p: LitProps) => JSX.Element + +const mismatched = {() => 12} +>mismatched : JSX.Element +>{() => 12} : JSX.Element +>ElemLit : (p: LitProps) => JSX.Element +>prop : "x" +>() => 12 : () => number +>12 : 12 +>ElemLit : (p: LitProps) => JSX.Element + diff --git a/tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx b/tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx new file mode 100644 index 0000000000000..4b4259574bf90 --- /dev/null +++ b/tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx @@ -0,0 +1,24 @@ +// @strict: true +// @jsx: preserve +namespace JSX { + export interface Element {} + export interface ElementAttributesProperty { props: {}; } + export interface ElementChildrenAttribute { children: {}; } + export interface IntrinsicAttributes {} + export interface IntrinsicElements { [key: string]: Element } +} +const Elem = (p: { prop: T, children: (t: T) => T }) =>
; +Elem({prop: {a: "x"}, children: i => ({a: "z"})}); +const q = ({a: "z"})} /> +const qq = {i => ({a: "z"})} + +interface LitProps { prop: T, children: (x: this) => T } +const ElemLit = (p: LitProps) =>
; +ElemLit({prop: "x", children: () => "x"}); +const j = "x"} /> +const jj = {() => "x"} + +// Should error +const arg = "y"} /> +const argchild = {p => "y"} +const mismatched = {() => 12} \ No newline at end of file From 122a7e32e3d4832a8a7c45d80bb27b7cc35f2c0f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 2 Feb 2018 17:35:43 -0800 Subject: [PATCH 4/7] Light code review feedback --- src/compiler/checker.ts | 78 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 32ac08400cc34..b7daafffb88e0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13901,7 +13901,7 @@ namespace ts { function getContextualTypeForChildJsxExpression(node: JsxElement) { const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName); // JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty) - const jsxChildrenPropertyName = getJsxElementChildrenPropertyname(); + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(); return attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "" ? getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName) : undefined; } @@ -14054,48 +14054,48 @@ namespace ts { return anyType; } - return mapType(valueType, signaturesToParameterTypes); + return mapType(valueType, getJsxSignaturesParameterTypes); + } - function signaturesToParameterTypes(valueType: Type) { - // If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type - if (valueType.flags & TypeFlags.String) { - return anyType; - } - else if (valueType.flags & TypeFlags.StringLiteral) { - // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type - // For example: - // var CustomTag: "h1" = "h1"; - // Hello World - const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements); - if (intrinsicElementsType !== unknownType) { - const stringLiteralTypeName = (valueType).value; - const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); - if (intrinsicProp) { - return getTypeOfSymbol(intrinsicProp); - } - const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); - if (indexSignatureType) { - return indexSignatureType; - } + function getJsxSignaturesParameterTypes(valueType: Type) { + // If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type + if (valueType.flags & TypeFlags.String) { + return anyType; + } + else if (valueType.flags & TypeFlags.StringLiteral) { + // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type + // For example: + // var CustomTag: "h1" = "h1"; + // Hello World + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements); + if (intrinsicElementsType !== unknownType) { + const stringLiteralTypeName = (valueType).value; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); + if (intrinsicProp) { + return getTypeOfSymbol(intrinsicProp); + } + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); + if (indexSignatureType) { + return indexSignatureType; } - return anyType; } + return anyType; + } - // Resolve the signatures, preferring constructor - let signatures = getSignaturesOfType(valueType, SignatureKind.Construct); - let ctor = true; + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(valueType, SignatureKind.Construct); + let ctor = true; + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(valueType, SignatureKind.Call); + ctor = false; if (signatures.length === 0) { - // No construct signatures, try call signatures - signatures = getSignaturesOfType(valueType, SignatureKind.Call); - ctor = false; - if (signatures.length === 0) { - // We found no signatures at all, which is an error - return unknownType; - } + // We found no signatures at all, which is an error + return unknownType; } - - return getUnionType(map(signatures, ctor ? getJsxPropsTypeFromConstructSignature : getJsxPropsTypeFromCallSignature), UnionReduction.None); } + + return getUnionType(map(signatures, ctor ? getJsxPropsTypeFromConstructSignature : getJsxPropsTypeFromCallSignature), UnionReduction.None); } function getJsxPropsTypeFromCallSignature(sig: Signature) { @@ -14688,7 +14688,7 @@ namespace ts { let hasSpreadAnyType = false; let typeToIntersect: Type; let explicitlySpecifyChildrenAttribute = false; - const jsxChildrenPropertyName = getJsxElementChildrenPropertyname(); + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(); for (const attributeDecl of attributes.properties) { const member = attributeDecl.symbol; @@ -14940,7 +14940,7 @@ namespace ts { return _jsxElementPropertiesName; } - function getJsxElementChildrenPropertyname(): __String { + function getJsxElementChildrenPropertyName(): __String { if (!_hasComputedJsxElementChildrenPropertyName) { _hasComputedJsxElementChildrenPropertyName = true; _jsxElementChildrenPropertyName = getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer); @@ -15331,7 +15331,7 @@ namespace ts { // 3. Check if the two are assignable to each other - // targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element. + // targetAttributesType is a type of an attribute from resolving tagName of an opening-like JSX element. const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ? getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) : getCustomJsxElementAttributesType(openingLikeElement, /*shouldIncludeAllStatelessAttributesType*/ false); From 59fc02589c0434bba41b1a802554e3b5811b3847 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 2 Feb 2018 18:04:29 -0800 Subject: [PATCH 5/7] Use fillMissingTypeArguments --- src/compiler/checker.ts | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b7daafffb88e0..499abc83d37d0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14054,10 +14054,19 @@ namespace ts { return anyType; } - return mapType(valueType, getJsxSignaturesParameterTypes); + const isJs = isInJavaScriptFile(node); + return mapType(valueType, isJs ? getJsxSignaturesParameterTypesJs : getJsxSignaturesParameterTypes); } function getJsxSignaturesParameterTypes(valueType: Type) { + return getJsxSignaturesParameterTypesInternal(valueType, /*isJs*/ false); + } + + function getJsxSignaturesParameterTypesJs(valueType: Type) { + return getJsxSignaturesParameterTypesInternal(valueType, /*isJs*/ true); + } + + function getJsxSignaturesParameterTypesInternal(valueType: Type, isJs: boolean) { // If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type if (valueType.flags & TypeFlags.String) { return anyType; @@ -14095,7 +14104,7 @@ namespace ts { } } - return getUnionType(map(signatures, ctor ? getJsxPropsTypeFromConstructSignature : getJsxPropsTypeFromCallSignature), UnionReduction.None); + return getUnionType(map(signatures, ctor ? isJs ? getJsxPropsTypeFromConstructSignatureJs : getJsxPropsTypeFromConstructSignature : getJsxPropsTypeFromCallSignature), UnionReduction.None); } function getJsxPropsTypeFromCallSignature(sig: Signature) { @@ -14107,7 +14116,7 @@ namespace ts { return propsType; } - function getJsxPropsTypeFromClassType(hostClassType: Type) { + function getJsxPropsTypeFromClassType(hostClassType: Type, isJs: boolean) { if (isTypeAny(hostClassType)) { return hostClassType; } @@ -14139,8 +14148,8 @@ namespace ts { if (intrinsicClassAttribs !== unknownType) { const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); apparentAttributesType = intersectTypes( - length(typeParams) - ? createTypeReference(intrinsicClassAttribs, [hostClassType]) + typeParams + ? createTypeReference(intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, typeParams.length, isJs)) : intrinsicClassAttribs, apparentAttributesType ); @@ -14156,14 +14165,24 @@ namespace ts { } } + function getJsxPropsTypeFromConstructSignatureJs(sig: Signature) { + return getJsxPropsTypeFromConstructSignatureInternal(sig, /*isJs*/ true); + } + function getJsxPropsTypeFromConstructSignature(sig: Signature) { + return getJsxPropsTypeFromConstructSignatureInternal(sig, /*isJs*/ false); + } + + function getJsxPropsTypeFromConstructSignatureInternal(sig: Signature, isJs: boolean) { const hostClassType = getReturnTypeOfSignature(sig); if (hostClassType) { - return getJsxPropsTypeFromClassType(hostClassType); + return getJsxPropsTypeFromClassType(hostClassType, isJs); } return getJsxPropsTypeFromCallSignature(sig); } + + // If the given type is an object or union type with a single signature, and if that signature has at // least as many parameters as the given function, return the signature. Otherwise return undefined. function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature { @@ -15129,7 +15148,7 @@ namespace ts { checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } - return getJsxPropsTypeFromClassType(elemInstanceType); + return getJsxPropsTypeFromClassType(elemInstanceType, isInJavaScriptFile(openingLikeElement)); } /** From 774aec86eb190e0cbc6ca4fcd52653aa4eab03ca Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Feb 2018 13:34:37 -0800 Subject: [PATCH 6/7] Accept nonliteral jsx child type --- .../checkJsxChildrenProperty2.errors.txt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/baselines/reference/checkJsxChildrenProperty2.errors.txt b/tests/baselines/reference/checkJsxChildrenProperty2.errors.txt index 0de31bf04b9a6..e2a6cf3a685cf 100644 --- a/tests/baselines/reference/checkJsxChildrenProperty2.errors.txt +++ b/tests/baselines/reference/checkJsxChildrenProperty2.errors.txt @@ -8,12 +8,12 @@ tests/cases/conformance/jsx/file.tsx(31,11): error TS2322: Type '{ children: (El Type '(Element | ((name: string) => Element))[]' is not assignable to type 'string | Element'. Type '(Element | ((name: string) => Element))[]' is not assignable to type 'Element'. Property 'type' is missing in type '(Element | ((name: string) => Element))[]'. -tests/cases/conformance/jsx/file.tsx(37,11): error TS2322: Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'. - Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'Prop'. +tests/cases/conformance/jsx/file.tsx(37,11): error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'. + Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'. Types of property 'children' are incompatible. - Type '(Element | 1000000)[]' is not assignable to type 'string | Element'. - Type '(Element | 1000000)[]' is not assignable to type 'Element'. - Property 'type' is missing in type '(Element | 1000000)[]'. + Type '(number | Element)[]' is not assignable to type 'string | Element'. + Type '(number | Element)[]' is not assignable to type 'Element'. + Property 'type' is missing in type '(number | Element)[]'. tests/cases/conformance/jsx/file.tsx(43,11): error TS2322: Type '{ children: (string | Element)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'. Type '{ children: (string | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'. Types of property 'children' are incompatible. @@ -80,12 +80,12 @@ tests/cases/conformance/jsx/file.tsx(49,11): error TS2322: Type '{ children: Ele let k3 = ~~~~~~~~~~~~~ -!!! error TS2322: Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'. -!!! error TS2322: Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'Prop'. +!!! error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'. +!!! error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'. !!! error TS2322: Types of property 'children' are incompatible. -!!! error TS2322: Type '(Element | 1000000)[]' is not assignable to type 'string | Element'. -!!! error TS2322: Type '(Element | 1000000)[]' is not assignable to type 'Element'. -!!! error TS2322: Property 'type' is missing in type '(Element | 1000000)[]'. +!!! error TS2322: Type '(number | Element)[]' is not assignable to type 'string | Element'. +!!! error TS2322: Type '(number | Element)[]' is not assignable to type 'Element'. +!!! error TS2322: Property 'type' is missing in type '(number | Element)[]'.
My Div
{1000000}
; From f1c97364c7a509fd811b9247c1088565196b6350 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 5 Feb 2018 15:05:35 -0800 Subject: [PATCH 7/7] Add test for the fillMissingTypeArguments case --- src/compiler/checker.ts | 2 +- .../reference/jsxElementClassTooManyParams.js | 27 ++++++++ .../jsxElementClassTooManyParams.symbols | 58 ++++++++++++++++++ .../jsxElementClassTooManyParams.types | 61 +++++++++++++++++++ .../compiler/jsxElementClassTooManyParams.tsx | 18 ++++++ 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/jsxElementClassTooManyParams.js create mode 100644 tests/baselines/reference/jsxElementClassTooManyParams.symbols create mode 100644 tests/baselines/reference/jsxElementClassTooManyParams.types create mode 100644 tests/cases/compiler/jsxElementClassTooManyParams.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 720cc3b20177e..d737991225bbe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14457,7 +14457,7 @@ namespace ts { const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); apparentAttributesType = intersectTypes( typeParams - ? createTypeReference(intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, typeParams.length, isJs)) + ? createTypeReference(intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isJs)) : intrinsicClassAttribs, apparentAttributesType ); diff --git a/tests/baselines/reference/jsxElementClassTooManyParams.js b/tests/baselines/reference/jsxElementClassTooManyParams.js new file mode 100644 index 0000000000000..e232f3392d7b1 --- /dev/null +++ b/tests/baselines/reference/jsxElementClassTooManyParams.js @@ -0,0 +1,27 @@ +//// [jsxElementClassTooManyParams.tsx] +namespace JSX { + export interface Element {} + export interface IntrinsicClassAttributes { + ref?: TClass; + item?: TOther; + } + export interface ElementClass extends Element {} + export interface ElementAttributesProperty { props: {}; } + export interface ElementChildrenAttribute { children: {}; } + export interface IntrinsicAttributes {} + export interface IntrinsicElements { [key: string]: Element } +} +class ElemClass implements JSX.ElementClass { + constructor(public props: T) {} +} +const elem = + +//// [jsxElementClassTooManyParams.jsx] +"use strict"; +var ElemClass = /** @class */ (function () { + function ElemClass(props) { + this.props = props; + } + return ElemClass; +}()); +var elem = ; diff --git a/tests/baselines/reference/jsxElementClassTooManyParams.symbols b/tests/baselines/reference/jsxElementClassTooManyParams.symbols new file mode 100644 index 0000000000000..dbb0f408450bd --- /dev/null +++ b/tests/baselines/reference/jsxElementClassTooManyParams.symbols @@ -0,0 +1,58 @@ +=== tests/cases/compiler/jsxElementClassTooManyParams.tsx === +namespace JSX { +>JSX : Symbol(JSX, Decl(jsxElementClassTooManyParams.tsx, 0, 0)) + + export interface Element {} +>Element : Symbol(Element, Decl(jsxElementClassTooManyParams.tsx, 0, 15)) + + export interface IntrinsicClassAttributes { +>IntrinsicClassAttributes : Symbol(IntrinsicClassAttributes, Decl(jsxElementClassTooManyParams.tsx, 1, 31)) +>TClass : Symbol(TClass, Decl(jsxElementClassTooManyParams.tsx, 2, 46)) +>TOther : Symbol(TOther, Decl(jsxElementClassTooManyParams.tsx, 2, 53)) + + ref?: TClass; +>ref : Symbol(IntrinsicClassAttributes.ref, Decl(jsxElementClassTooManyParams.tsx, 2, 69)) +>TClass : Symbol(TClass, Decl(jsxElementClassTooManyParams.tsx, 2, 46)) + + item?: TOther; +>item : Symbol(IntrinsicClassAttributes.item, Decl(jsxElementClassTooManyParams.tsx, 3, 21)) +>TOther : Symbol(TOther, Decl(jsxElementClassTooManyParams.tsx, 2, 53)) + } + export interface ElementClass extends Element {} +>ElementClass : Symbol(ElementClass, Decl(jsxElementClassTooManyParams.tsx, 5, 5)) +>Element : Symbol(Element, Decl(jsxElementClassTooManyParams.tsx, 0, 15)) + + export interface ElementAttributesProperty { props: {}; } +>ElementAttributesProperty : Symbol(ElementAttributesProperty, Decl(jsxElementClassTooManyParams.tsx, 6, 52)) +>props : Symbol(ElementAttributesProperty.props, Decl(jsxElementClassTooManyParams.tsx, 7, 48)) + + export interface ElementChildrenAttribute { children: {}; } +>ElementChildrenAttribute : Symbol(ElementChildrenAttribute, Decl(jsxElementClassTooManyParams.tsx, 7, 61)) +>children : Symbol(ElementChildrenAttribute.children, Decl(jsxElementClassTooManyParams.tsx, 8, 47)) + + export interface IntrinsicAttributes {} +>IntrinsicAttributes : Symbol(IntrinsicAttributes, Decl(jsxElementClassTooManyParams.tsx, 8, 63)) + + export interface IntrinsicElements { [key: string]: Element } +>IntrinsicElements : Symbol(IntrinsicElements, Decl(jsxElementClassTooManyParams.tsx, 9, 43)) +>key : Symbol(key, Decl(jsxElementClassTooManyParams.tsx, 10, 42)) +>Element : Symbol(Element, Decl(jsxElementClassTooManyParams.tsx, 0, 15)) +} +class ElemClass implements JSX.ElementClass { +>ElemClass : Symbol(ElemClass, Decl(jsxElementClassTooManyParams.tsx, 11, 1)) +>T : Symbol(T, Decl(jsxElementClassTooManyParams.tsx, 12, 16)) +>x : Symbol(x, Decl(jsxElementClassTooManyParams.tsx, 12, 27)) +>JSX.ElementClass : Symbol(JSX.ElementClass, Decl(jsxElementClassTooManyParams.tsx, 5, 5)) +>JSX : Symbol(JSX, Decl(jsxElementClassTooManyParams.tsx, 0, 0)) +>ElementClass : Symbol(JSX.ElementClass, Decl(jsxElementClassTooManyParams.tsx, 5, 5)) + + constructor(public props: T) {} +>props : Symbol(ElemClass.props, Decl(jsxElementClassTooManyParams.tsx, 13, 16)) +>T : Symbol(T, Decl(jsxElementClassTooManyParams.tsx, 12, 16)) +} +const elem = +>elem : Symbol(elem, Decl(jsxElementClassTooManyParams.tsx, 15, 5)) +>ElemClass : Symbol(ElemClass, Decl(jsxElementClassTooManyParams.tsx, 11, 1)) +>x : Symbol(x, Decl(jsxElementClassTooManyParams.tsx, 15, 23)) +>y : Symbol(y, Decl(jsxElementClassTooManyParams.tsx, 15, 30)) + diff --git a/tests/baselines/reference/jsxElementClassTooManyParams.types b/tests/baselines/reference/jsxElementClassTooManyParams.types new file mode 100644 index 0000000000000..c0237c6a2dd97 --- /dev/null +++ b/tests/baselines/reference/jsxElementClassTooManyParams.types @@ -0,0 +1,61 @@ +=== tests/cases/compiler/jsxElementClassTooManyParams.tsx === +namespace JSX { +>JSX : any + + export interface Element {} +>Element : Element + + export interface IntrinsicClassAttributes { +>IntrinsicClassAttributes : IntrinsicClassAttributes +>TClass : TClass +>TOther : TOther + + ref?: TClass; +>ref : TClass | undefined +>TClass : TClass + + item?: TOther; +>item : TOther | undefined +>TOther : TOther + } + export interface ElementClass extends Element {} +>ElementClass : ElementClass +>Element : Element + + export interface ElementAttributesProperty { props: {}; } +>ElementAttributesProperty : ElementAttributesProperty +>props : {} + + export interface ElementChildrenAttribute { children: {}; } +>ElementChildrenAttribute : ElementChildrenAttribute +>children : {} + + export interface IntrinsicAttributes {} +>IntrinsicAttributes : IntrinsicAttributes + + export interface IntrinsicElements { [key: string]: Element } +>IntrinsicElements : IntrinsicElements +>key : string +>Element : Element +} +class ElemClass implements JSX.ElementClass { +>ElemClass : ElemClass +>T : T +>x : number +>JSX.ElementClass : any +>JSX : any +>ElementClass : JSX.ElementClass + + constructor(public props: T) {} +>props : T +>T : T +} +const elem = +>elem : JSX.Element +> : JSX.Element +>ElemClass : typeof ElemClass +>x : number +>12 : 12 +>y : number +>24 : 24 + diff --git a/tests/cases/compiler/jsxElementClassTooManyParams.tsx b/tests/cases/compiler/jsxElementClassTooManyParams.tsx new file mode 100644 index 0000000000000..25eedb348d859 --- /dev/null +++ b/tests/cases/compiler/jsxElementClassTooManyParams.tsx @@ -0,0 +1,18 @@ +// @strict: true +// @jsx: preserve +namespace JSX { + export interface Element {} + export interface IntrinsicClassAttributes { + ref?: TClass; + item?: TOther; + } + export interface ElementClass extends Element {} + export interface ElementAttributesProperty { props: {}; } + export interface ElementChildrenAttribute { children: {}; } + export interface IntrinsicAttributes {} + export interface IntrinsicElements { [key: string]: Element } +} +class ElemClass implements JSX.ElementClass { + constructor(public props: T) {} +} +const elem = \ No newline at end of file