Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 13.7k
Allow calls on unions of dissimilar signatures#29011
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
81840376c2e1e18ea94cd407054ad7c668a5df3c69f24c7f5d28d87ff1d78e99c7abc42ef6733875bb0e7b2ad7fFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -6799,7 +6799,12 @@ namespace ts { | ||
| // type is the union of the constituent return types. | ||
| function getUnionSignatures(signatureLists: ReadonlyArray<ReadonlyArray<Signature>>): Signature[] { | ||
| let result: Signature[] | undefined; | ||
| let indexWithLengthOverOne: number | undefined; | ||
| for (let i = 0; i < signatureLists.length; i++) { | ||
| if (signatureLists[i].length === 0) return emptyArray; | ||
| if (signatureLists[i].length > 1) { | ||
| indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets | ||
weswigham marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| for (const signature of signatureLists[i]) { | ||
| // Only process signatures with parameter lists that aren't already in the result list | ||
| if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true)) { | ||
| @@ -6823,9 +6828,91 @@ namespace ts { | ||
| } | ||
| } | ||
| } | ||
| if (!length(result) && indexWithLengthOverOne !== -1) { | ||
weswigham marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single | ||
| // signature that handles all over them. We only do this when there are overloads in only one constituent. | ||
| // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of | ||
| // signatures from the type, whose ordering would be non-obvious) | ||
| const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; | ||
| let results: Signature[] | undefined = masterList.slice(); | ||
| for (const signatures of signatureLists) { | ||
| if (signatures !== masterList) { | ||
| const signature = signatures[0]; | ||
| Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); | ||
| results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); | ||
| if (!results) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| result = results; | ||
| } | ||
| return result || emptyArray; | ||
weswigham marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
weswigham marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined): Symbol | undefined { | ||
| if (!left || !right) { | ||
| return left || right; | ||
weswigham marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| // A signature `this` type might be a read or a write position... It's very possible that it should be invariant | ||
| // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be | ||
| // permissive when calling, for now, we'll union the `this` types just like the overlapping-union-signature check does | ||
weswigham marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| const thisType = getUnionType([getTypeOfSymbol(left), getTypeOfSymbol(right)], UnionReduction.Subtype); | ||
| return createSymbolWithType(left, thisType); | ||
| } | ||
| function combineUnionParameters(left: Signature, right: Signature) { | ||
weswigham marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| const longest = getParameterCount(left) >= getParameterCount(right) ? left : right; | ||
| const shorter = longest === left ? right : left; | ||
| const longestCount = getParameterCount(longest); | ||
| const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); | ||
| const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); | ||
| const params = new Array<Symbol>(longestCount + (needsExtraRestElement ? 1 : 0)); | ||
| for (let i = 0; i < longestCount; i++) { | ||
| const longestParamType = tryGetTypeAtPosition(longest, i)!; | ||
| const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; | ||
| const unionParamType = getIntersectionType([longestParamType, shorterParamType]); | ||
| const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); | ||
| const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); | ||
| const leftName = getParameterNameAtPosition(left, i); | ||
| const rightName = getParameterNameAtPosition(right, i); | ||
| const paramSymbol = createSymbol( | ||
| SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), | ||
| leftName === rightName ? leftName : `arg${i}` as __String | ||
| ); | ||
| paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; | ||
| params[i] = paramSymbol; | ||
| } | ||
| if (needsExtraRestElement) { | ||
| const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); | ||
| restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); | ||
| params[longestCount] = restParamSymbol; | ||
| } | ||
| return params; | ||
| } | ||
| function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { | ||
| const declaration = left.declaration; | ||
| const params = combineUnionParameters(left, right); | ||
| const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter); | ||
| const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); | ||
| const hasRestParam = left.hasRestParameter || right.hasRestParameter; | ||
| const hasLiteralTypes = left.hasLiteralTypes || right.hasLiteralTypes; | ||
| const result = createSignature( | ||
| declaration, | ||
| left.typeParameters || right.typeParameters, | ||
| thisParam, | ||
| params, | ||
| /*resolvedReturnType*/ undefined, | ||
| /*resolvedTypePredicate*/ undefined, | ||
| minArgCount, | ||
| hasRestParam, | ||
| hasLiteralTypes | ||
| ); | ||
| result.unionSignatures = concatenate(left.unionSignatures || [left], [right]); | ||
| return result; | ||
| } | ||
| function getUnionIndexInfo(types: ReadonlyArray<Type>, kind: IndexKind): IndexInfo | undefined { | ||
| const indexTypes: Type[] = []; | ||
| let isAnyReadonly = false; | ||
| @@ -17566,6 +17653,26 @@ namespace ts { | ||
| } | ||
| function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { | ||
| if (sig.unionSignatures) { | ||
| // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input | ||
| // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, | ||
| // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur | ||
| // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. | ||
| // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. | ||
| const results: Type[] = []; | ||
| for (const signature of sig.unionSignatures) { | ||
| const instance = getReturnTypeOfSignature(signature); | ||
| if (isTypeAny(instance)) { | ||
| return instance; | ||
| } | ||
| const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); | ||
| if (!propType) { | ||
| return; | ||
| } | ||
| results.push(propType); | ||
| } | ||
| return getIntersectionType(results); | ||
weswigham marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| const instanceType = getReturnTypeOfSignature(sig); | ||
| return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| tests/cases/compiler/callsOnComplexSignatures.tsx(38,19): error TS7006: Parameter 'item' implicitly has an 'any' type. | ||
| ==== tests/cases/compiler/callsOnComplexSignatures.tsx (1 errors) ==== | ||
| /// <reference path="/.lib/react16.d.ts" /> | ||
| import React from "react"; | ||
| // Simple calls from real usecases | ||
| function test1() { | ||
| type stringType1 = "foo" | "bar"; | ||
| type stringType2 = "baz" | "bar"; | ||
| interface Temp1 { | ||
| getValue(name: stringType1): number; | ||
| } | ||
| interface Temp2 { | ||
| getValue(name: stringType2): string; | ||
| } | ||
| function test(t: Temp1 | Temp2) { | ||
| const z = t.getValue("bar"); // Should be fine | ||
| } | ||
| } | ||
| function test2() { | ||
| interface Messages { | ||
| readonly foo: (options: { [key: string]: any, b: number }) => string; | ||
| readonly bar: (options: { [key: string]: any, a: string }) => string; | ||
| } | ||
| const messages: Messages = { | ||
| foo: (options) => "Foo", | ||
| bar: (options) => "Bar", | ||
| }; | ||
| const test1 = (type: "foo" | "bar") => | ||
| messages[type]({ a: "A", b: 0 }); | ||
| } | ||
| function test3(items: string[] | number[]) { | ||
| items.forEach(item => console.log(item)); | ||
| ~~~~ | ||
| !!! error TS7006: Parameter 'item' implicitly has an 'any' type. | ||
| } | ||
| function test4( | ||
| arg1: ((...objs: {x: number}[]) => number) | ((...objs: {y: number}[]) => number), | ||
| arg2: ((a: {x: number}, b: object) => number) | ((a: object, b: {x: number}) => number), | ||
| arg3: ((a: {x: number}, ...objs: {y: number}[]) => number) | ((...objs: {x: number}[]) => number), | ||
| arg4: ((a?: {x: number}, b?: {x: number}) => number) | ((a?: {y: number}) => number), | ||
| arg5: ((a?: {x: number}, ...b: {x: number}[]) => number) | ((a?: {y: number}) => number), | ||
| arg6: ((a?: {x: number}, b?: {x: number}) => number) | ((...a: {y: number}[]) => number), | ||
| ) { | ||
| arg1(); | ||
| arg1({x: 0, y: 0}); | ||
| arg1({x: 0, y: 0}, {x: 1, y: 1}); | ||
| arg2({x: 0}, {x: 0}); | ||
| arg3({x: 0}); | ||
| arg3({x: 0}, {x: 0, y: 0}); | ||
| arg3({x: 0}, {x: 0, y: 0}, {x: 0, y: 0}); | ||
| arg4(); | ||
| arg4({x: 0, y: 0}); | ||
| arg4({x: 0, y: 0}, {x: 0}); | ||
| arg5(); | ||
| arg5({x: 0, y: 0}); | ||
| arg5({x: 0, y: 0}, {x: 0}); | ||
| arg6(); | ||
| arg6({x: 0, y: 0}); | ||
| arg6({x: 0, y: 0}, {x: 0, y: 0}); | ||
| arg6({x: 0, y: 0}, {x: 0, y: 0}, {y: 0}); | ||
| } | ||
| // JSX Tag names | ||
| function test5() { | ||
| // Pair of non-like intrinsics | ||
| function render(url?: string): React.ReactNode { | ||
| const Tag = url ? 'a' : 'button'; | ||
| return <Tag>test</Tag>; | ||
| } | ||
| // Union of all intrinsics and components of `any` | ||
| function App(props: { component:React.ReactType }) { | ||
| const Comp: React.ReactType = props.component; | ||
| return (<Comp />); | ||
| } | ||
| // custom components with non-subset props | ||
| function render2() { | ||
| interface P1 { | ||
| p?: boolean; | ||
| c?: string; | ||
| } | ||
| interface P2 { | ||
| p?: boolean; | ||
| c?: any; | ||
| d?: any; | ||
| } | ||
| var C: React.ComponentType<P1> | React.ComponentType<P2> = null as any; | ||
| const a = <C p={true} />; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.