@@ -6799,7 +6799,12 @@ namespace ts {
67996799 // type is the union of the constituent return types.
68006800 function getUnionSignatures(signatureLists: ReadonlyArray<ReadonlyArray<Signature>>): Signature[] {
68016801 let result: Signature[] | undefined;
6802+ let indexWithLengthOverOne: number | undefined;
68026803 for (let i = 0; i < signatureLists.length; i++) {
6804+ if (signatureLists[i].length === 0) return emptyArray;
6805+ if (signatureLists[i].length > 1) {
6806+ indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets
6807+ }
68036808 for (const signature of signatureLists[i]) {
68046809 // Only process signatures with parameter lists that aren't already in the result list
68056810 if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true)) {
@@ -6823,9 +6828,91 @@ namespace ts {
68236828 }
68246829 }
68256830 }
6831+ if (!length(result) && indexWithLengthOverOne !== -1) {
6832+ // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single
6833+ // signature that handles all over them. We only do this when there are overloads in only one constituent.
6834+ // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of
6835+ // signatures from the type, whose ordering would be non-obvious)
6836+ const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0];
6837+ let results: Signature[] | undefined = masterList.slice();
6838+ for (const signatures of signatureLists) {
6839+ if (signatures !== masterList) {
6840+ const signature = signatures[0];
6841+ Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass");
6842+ results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature));
6843+ if (!results) {
6844+ break;
6845+ }
6846+ }
6847+ }
6848+ result = results;
6849+ }
68266850 return result || emptyArray;
68276851 }
68286852
6853+ function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined): Symbol | undefined {
6854+ if (!left || !right) {
6855+ return left || right;
6856+ }
6857+ // A signature `this` type might be a read or a write position... It's very possible that it should be invariant
6858+ // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be
6859+ // permissive when calling, for now, we'll union the `this` types just like the overlapping-union-signature check does
6860+ const thisType = getUnionType([getTypeOfSymbol(left), getTypeOfSymbol(right)], UnionReduction.Subtype);
6861+ return createSymbolWithType(left, thisType);
6862+ }
6863+
6864+ function combineUnionParameters(left: Signature, right: Signature) {
6865+ const longest = getParameterCount(left) >= getParameterCount(right) ? left : right;
6866+ const shorter = longest === left ? right : left;
6867+ const longestCount = getParameterCount(longest);
6868+ const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right));
6869+ const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest);
6870+ const params = new Array<Symbol>(longestCount + (needsExtraRestElement ? 1 : 0));
6871+ for (let i = 0; i < longestCount; i++) {
6872+ const longestParamType = tryGetTypeAtPosition(longest, i)!;
6873+ const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType;
6874+ const unionParamType = getIntersectionType([longestParamType, shorterParamType]);
6875+ const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1);
6876+ const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter);
6877+ const leftName = getParameterNameAtPosition(left, i);
6878+ const rightName = getParameterNameAtPosition(right, i);
6879+ const paramSymbol = createSymbol(
6880+ SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0),
6881+ leftName === rightName ? leftName : `arg${i}` as __String
6882+ );
6883+ paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType;
6884+ params[i] = paramSymbol;
6885+ }
6886+ if (needsExtraRestElement) {
6887+ const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String);
6888+ restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount));
6889+ params[longestCount] = restParamSymbol;
6890+ }
6891+ return params;
6892+ }
6893+
6894+ function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature {
6895+ const declaration = left.declaration;
6896+ const params = combineUnionParameters(left, right);
6897+ const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter);
6898+ const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount);
6899+ const hasRestParam = left.hasRestParameter || right.hasRestParameter;
6900+ const hasLiteralTypes = left.hasLiteralTypes || right.hasLiteralTypes;
6901+ const result = createSignature(
6902+ declaration,
6903+ left.typeParameters || right.typeParameters,
6904+ thisParam,
6905+ params,
6906+ /*resolvedReturnType*/ undefined,
6907+ /*resolvedTypePredicate*/ undefined,
6908+ minArgCount,
6909+ hasRestParam,
6910+ hasLiteralTypes
6911+ );
6912+ result.unionSignatures = concatenate(left.unionSignatures || [left], [right]);
6913+ return result;
6914+ }
6915+
68296916 function getUnionIndexInfo(types: ReadonlyArray<Type>, kind: IndexKind): IndexInfo | undefined {
68306917 const indexTypes: Type[] = [];
68316918 let isAnyReadonly = false;
@@ -17566,6 +17653,26 @@ namespace ts {
1756617653 }
1756717654
1756817655 function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) {
17656+ if (sig.unionSignatures) {
17657+ // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input
17658+ // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature,
17659+ // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur
17660+ // 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.
17661+ // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane.
17662+ const results: Type[] = [];
17663+ for (const signature of sig.unionSignatures) {
17664+ const instance = getReturnTypeOfSignature(signature);
17665+ if (isTypeAny(instance)) {
17666+ return instance;
17667+ }
17668+ const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation);
17669+ if (!propType) {
17670+ return;
17671+ }
17672+ results.push(propType);
17673+ }
17674+ return getIntersectionType(results);
17675+ }
1756917676 const instanceType = getReturnTypeOfSignature(sig);
1757017677 return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
1757117678 }
0 commit comments