Skip to content

Issue 19220 function parameter arity #24031

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

Merged
merged 19 commits into from
Jul 12, 2018

Conversation

Kr4pper
Copy link
Contributor

@Kr4pper Kr4pper commented May 10, 2018

Fixes #19220

Further changes could be made to rigorously check all available overload signatures in order to identify cases where emitting possible overloads as a range is applicable and thus respond with more accurate diagnostics.

@msftclas
Copy link

msftclas commented May 10, 2018

CLA assistant check
All CLA requirements met.

@Kr4pper
Copy link
Contributor Author

Kr4pper commented May 10, 2018

I am currently working on further analyzing available signatures to convey more useful error information in complex cases.

@Kr4pper
Copy link
Contributor Author

Kr4pper commented May 11, 2018

After further investigating all possible cases of function parameter arity mismatches I determined that adding them to this pull would require too many code changes. It would however be possible and relatively straightword to add those cases as more accurate diagnostics in the future.

@Kr4pper Kr4pper force-pushed the issue-19220-arity-error-msg branch from ace037c to 3998c54 Compare May 12, 2018 13:01
@@ -20,7 +20,7 @@ verify.currentSignatureParameterCountIs(2);
verify.currentParameterHelpArgumentNameIs("b");
verify.currentParameterSpanIs("b: boolean");

edit.insert("x, ");
// edit.insert("x, "); // TODO: This block looks like a duplicate of the above code
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this test case necessary?

@Kr4pper Kr4pper force-pushed the issue-19220-arity-error-msg branch from 7dd99e8 to 9cf84de Compare May 20, 2018 12:31
@mhegazy mhegazy requested a review from sandersn May 25, 2018 22:59
@mhegazy
Copy link
Contributor

mhegazy commented May 25, 2018

@sandersn can you please review this change.

@Kr4pper Kr4pper force-pushed the issue-19220-arity-error-msg branch from 9cf84de to 10e2dd3 Compare July 6, 2018 08:18
@Kr4pper
Copy link
Contributor Author

Kr4pper commented Jul 6, 2018

@sandersn This PR is now up to date with master again.

@sandersn
Copy link
Member

sandersn commented Jul 9, 2018

@rFlorian Just a brief note. I started looking at the PR.

However, I don't believe this is the right solution. I believe that it catches a case (discontinuous overload sets) where the target function is wrong, not the calling code, and so the error message should (1) point the user to the target function (2) be much easier to implement, since it's a rare case.

I don't have data to back me up yet, but I'm in the middle of making a version of the compiler that fails whenever it sees a discontinuous overload set, after which I'll run it on Definitely Typed to see whether real authors ever intentionally write them. I'll report back what I find.

@sandersn
Copy link
Member

sandersn commented Jul 9, 2018

OK, I've got results from the experiment. Turns out I was wrong. The DOM, jquery and node all have discontinuous overload arities, as do many other projects, including Typescript itself. A few of these I looked at are just fussy, but do aid correctness. The others all follow a similar pattern: Sets of parameters get added in logical groups, such as x/y pairs. An example is (sourceX, sourceY); (sourceX, sourceY, targetX, targetY), where the second overload lets you specify both source and target locations.

So I do think this change is necessary. There is still a trade off between smart messages backed by very complex code versus a simple message backed by simple code. I'm not sure where we should end up on that; I'll take a closer look at the code to help me decide.

In case anybody wants to replicate the experiment, here is the code:

        function kaboom(signatures: ReadonlyArray<Signature>) {
            if (length(signatures) === 0) return;
            let spans = [];
            let min = Number.POSITIVE_INFINITY;
            let max = Number.NEGATIVE_INFINITY;
            for (const sig of signatures) {
                let x = { min: getMinArgumentCount(sig), max: getParameterCount(sig) }
                spans.push(x);
                min = Math.min(min, x.min);
                max = Math.max(max, x.max);
            }
            for (var i = min; i <= max; i++) {
                if (!find(spans, ({min,max}) => min <= i && i <= max)) {
                    console.log("Couldn't find " + i + " at " + getTextOfNode(signatures[0].declaration!));
                }
            }
        }

// ... early in checkFunctionOrMethodDeclaration ...
            const t = getTypeOfSymbol(getSymbolOfNode(node))
            const signatures = getSignaturesOfType(t, SignatureKind.Call);
            kaboom(signatures);

It's slow and doesn't handle rest parameters quite right but is good enough to find lots of examples.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure the implementation can be made more efficient (and somewhat simpler). Also I would like a slightly different wording of the error message.

@@ -26,7 +26,7 @@ tests/cases/conformance/types/rest/genericRestParameters1.ts(31,1): error TS2556
f1(ns[0], ns[1], true);
f1(...ns, true); // Error, tuple spread only expanded when last
~~~~~~~~~~~~~~~
!!! error TS2556: Expected 3 arguments, but got 1 or more.
!!! error TS2557: Expected at least 0 arguments, but got 2 or more.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR needs to work with the new tuple-spreading code. Both the expected and actual argument count are now wrong.

@@ -5344,6 +5344,7 @@ declare namespace ts {
Rest_signatures_are_incompatible: DiagnosticMessage;
Property_0_is_incompatible_with_rest_element_type: DiagnosticMessage;
A_rest_element_type_must_be_an_array_type: DiagnosticMessage;
No_overload_expects_0_arguments_The_most_likely_overloads_that_match_expect_either_1_arguments_or_at_least_2_arguments: DiagnosticMessage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would reword this like so:

No overload expects 1 arguments, but overloads do exist that expect either 0 or at least 2 arguments.

const hasRestParameter = some(signatures, hasEffectiveRestParameter);
const availableArgumentCounts: number[] = [];
forEach(signatures, (sig: Signature) => {
const maxArgs = sig.parameters.length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixing the test failures might be as simple as calling the new helpers getMinArgumentCount and getParameterCount instead of directly accessing properties.

undefined;
diagnostics.add(createDiagnosticForNode(node, <DiagnosticMessage>error, paramRange, argCount));
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collapse to else if

const error = hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 :
hasSpreadArgument ? Diagnostics.Expected_0_arguments_but_got_1_or_more :
undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skip the hasSpreadArgument check and just return Expected 0 arguments but got 1 or more in the else branch of hasRestParameter.

@@ -19043,26 +19043,48 @@ namespace ts {
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments));
}
else if (args) {
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should revert all the first section of this change and add two more variables. This worked for me in simple tests:

let belowArgCount = Number.NEGATIVE_INFINITY
let aboveArgCount = Number.POSITIVE_INFINITY
for (const sig of signatures) {
    minCount = getMinArgumentCount(sig)
    maxCount = getParameterCount(sig)
    if (minCount < argCount && argCount - minCount < argCount - belowArgCount) belowArgCount = minCount
    if (argCount < maxCount && maxCount - argCount < aboveArgCount - argCount) aboveArgCount = maxCount
    min = Math.min(min, minCount)
    max = Math.max(max, maxCount)
}

(Note that you'll have to calculate argCount earlier.) Then you can use belowArgCount/aboveArgCount in the new error reporting branch you added in the second half of this change.

@@ -26,7 +26,7 @@ tests/cases/conformance/types/rest/genericRestParameters1.ts(31,1): error TS2556
f1(ns[0], ns[1], true);
f1(...ns, true); // Error, tuple spread only expanded when last
~~~~~~~~~~~~~~~
!!! error TS2556: Expected 3 arguments, but got 1 or more.
!!! error TS2557: Expected at least 3 arguments, but got 1 or more.
Copy link
Contributor Author

@Kr4pper Kr4pper Jul 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These still emit a slightly changed error message. Is the changed behavior in this case wanted and/or acceptable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. It's not desired, because f1 accepts exactly 3 arguments. There must be some way to notice that the rest param is a tuple type (with known length).

@@ -2056,6 +2056,10 @@
"category": "Error",
"code": 2574
},
"No overload expects {0} arguments, but overloads do exist that expect either {1} or at least {2} arguments.": {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added your simplified error message, however I feel like it might hide information from the user in cases where discontinuous overload sets with argument counts less than the current argument count exist.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, for example, 4 arguments provided to an overload set that has arity [1,2,5,6,7] ? It will report "Overloads do exist that expect either 2 or at least 5 arguments"

Actually, after thinking a bit more, I'd rather go the other direction and say "Overloads do exist that expect either 2 or 5 arguments". It points out the nearest arities and is terse enough to avoid implying that it has enumerated all possible arities.

@Kr4pper
Copy link
Contributor Author

Kr4pper commented Jul 10, 2018

@sandersn Thank you for the detailed analysis and hints on the PR. I incorporated your suggestions into the PR with slight modifications. I do not fully understand the requirements for diagnostics generation with regards to the new new tuple-spreading code so those might not work fully as intended.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test change in genericRestParameters1 shouldn't happen -- I think there is a check that now exists that you can use to find out whether the rest parameter is a tuple type. Maybe look at hasEffectiveRestParameter and its callers?

Plus a few small style changes.

@@ -26,7 +26,7 @@ tests/cases/conformance/types/rest/genericRestParameters1.ts(31,1): error TS2556
f1(ns[0], ns[1], true);
f1(...ns, true); // Error, tuple spread only expanded when last
~~~~~~~~~~~~~~~
!!! error TS2556: Expected 3 arguments, but got 1 or more.
!!! error TS2557: Expected at least 3 arguments, but got 1 or more.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. It's not desired, because f1 accepts exactly 3 arguments. There must be some way to notice that the rest param is a tuple type (with known length).

const hasSpreadArgument = getSpreadArgumentIndex(args) > -1;
const paramCount = hasRestParameter ? min :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert lines 19062-19069 ( if (argCount <= ... min; )

diagnostics.add(createDiagnosticForNode(node, error, paramCount, argCount));
if (argCount <= max && hasSpreadArgument) argCount--;

const hasRestParameter = some(signatures, (sig: Signature) => sig.hasRestParameter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should still be some(signatures, hasEffectiveRestParameter)

@@ -2056,6 +2056,10 @@
"category": "Error",
"code": 2574
},
"No overload expects {0} arguments, but overloads do exist that expect either {1} or at least {2} arguments.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, for example, 4 arguments provided to an overload set that has arity [1,2,5,6,7] ? It will report "Overloads do exist that expect either 2 or at least 5 arguments"

Actually, after thinking a bit more, I'd rather go the other direction and say "Overloads do exist that expect either 2 or 5 arguments". It points out the nearest arities and is terse enough to avoid implying that it has enumerated all possible arities.

@Kr4pper Kr4pper force-pushed the issue-19220-arity-error-msg branch from e1ebdcf to 3f8a69d Compare July 11, 2018 22:27
@Kr4pper
Copy link
Contributor Author

Kr4pper commented Jul 11, 2018

When checking for rest parameters, hasEffectiveRestParameter is now properly utilized again. This fixed both cases where tuple unpacking parameters were erroneously assessed as rest parameters.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I'll merge after you revert a couple of small formatting changes.

Diagnostics.Expected_0_arguments_but_got_1;
diagnostics.add(createDiagnosticForNode(node, error, paramCount, argCount));
const hasSpreadArgument = getSpreadArgumentIndex(args) > -1;
if (argCount <= max && hasSpreadArgument) argCount--;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change too. (I'm not sure whether the linter will let it through anyway.)


if (hasRestParameter || hasSpreadArgument) {
const error = hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 : Diagnostics.Expected_0_arguments_but_got_1_or_more;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this line-join and put Diagnostics.Expected 0 arguments but got 1 or more back on its own line.

@Kr4pper
Copy link
Contributor Author

Kr4pper commented Jul 11, 2018

Sounds good, I believe everything is now formatted as expected.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I'll merge after the CI finishes.

@sandersn sandersn merged commit 66e9aaa into microsoft:master Jul 12, 2018
@sandersn
Copy link
Member

Thanks for all your work on this one @rFlorian !

@Kr4pper
Copy link
Contributor Author

Kr4pper commented Jul 12, 2018

Thanks for helping me with the PR @sandersn !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants