Skip to content
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

Better error message for accidental calls to get-accessors #37800

Merged
merged 8 commits into from
May 21, 2020

Conversation

jtbandes
Copy link
Contributor

@jtbandes jtbandes commented Apr 5, 2020

Fixes #24554 by adding a new Message when resolveCallExpression fails to find any call signatures, if the node.expression references a GetAccessor. Example test case with new message:

==== tests/cases/compiler/accessorAccidentalCallDiagnostic.ts (1 errors) ====
    // https://github.com/microsoft/TypeScript/issues/24554
    class Test24554 {
        get property(): number { return 1; }
    }
    function test24554(x: Test24554) {
        return x.property();
                 ~~~~~~~~
!!! error TS2349: This expression is not callable.
!!! error TS2349:   Type 'Number' has no call signatures.
!!! related TS6232 tests/cases/compiler/accessorAccidentalCallDiagnostic.ts:6:12: 'x.property' is a 'get' accessor; did you mean to use it without '()'?
!!! related TS2728 tests/cases/compiler/accessorAccidentalCallDiagnostic.ts:3:9: 'property' is declared here.
    }

This is my first attempt to contribute to TS, so I'm eager for any feedback! Specifically here are some questions I'd like someone familiar with the code to answer:

  1. Was I right to just choose an available code in diagnosticMessages.json? Are they arranged in some grouping other than by code?
  2. Is resolveCallExpression a good place to add this? The discussion on Clarify error message when an accessor is invoked. #27897 mentioned changing checkPropertyAccessExpression, but that seemed hard to me because the PropertyAccessExpression isn't really aware of its parent CallExpression (well, I guess it might be able to access parent, but that doesn't seem great—and there are some other diagnostics in this function already).
  3. Any caveats to using getNodeLinks(node.expression).resolvedSymbol? That was the easiest way I found to reach the function and determine whether it's a GetAccessor, but it also seems to implicitly depend on something else having resolved the symbol first.
  4. Is relatedInformation the right place to attach such a message? I'm a bit confused about the diagnostic "chain" and how relatedInfo fits in, but this seems to work.
  5. I'd be interested in adding a code fix to delete the () as well. I started down that path, but it seems like fixes can't be triggered on relatedInformation, just on errors (I was able to create a new fix that got triggered on This_expression_is_not_callable errors, but it didn't trigger if I register it only for my new diagnostic message.) And since the fix's getCodeActions(context) function doesn't receive the diagnostic itself, just the code, it doesn't seem feasible to register it for all This_expression_is_not_callable errors and look at their relatedInformation to see if my new did you mean to use it without '()'? is attached. One approach I could consider is making it a separate error, but then it wouldn't be attached to the existing "not callable" error. Any advice here?

@msftclas
Copy link

msftclas commented Apr 5, 2020

CLA assistant check
All CLA requirements met.

@DanielRosenwasser
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 9, 2020

Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at f92edb7. You can monitor the build here.

src/compiler/checker.ts Outdated Show resolved Hide resolved
@@ -4384,6 +4384,10 @@
"category": "Error",
"code": 6231
},
"'{0}' is a 'get' accessor; did you mean to use it without '()'?": {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"'{0}' is a 'get' accessor; did you mean to use it without '()'?": {
"'{0}' is declared as a 'get' accessor and does not need to be called.": {

Copy link
Member

Choose a reason for hiding this comment

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

Actually, trying this out in an editor, I could see either one being good. It's up to you.

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 could see the wording that mentions () being better if we could actually provide a code fix that deletes the (). I had some questions about that in my PR description, would you happen to have experience with code fixes and could help me figure out how to implement it cleanly?

Copy link
Member

Choose a reason for hiding this comment

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

With or without the codefix, I like the () message better since it shows what's wrong instead of naming it in terms that not everyone understands immediately.

Copy link
Member

Choose a reason for hiding this comment

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

re codefixes: There are some simple ones that you can basically copy, then you just have to learn how to extract the property access from the call, which should also be easy.

However, I'd rather just take the error alone and follow up with the codefix.

Copy link
Member

@sandersn sandersn Apr 15, 2020

Choose a reason for hiding this comment

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

Actually, as a replacement for 'this expression is not callable', we should keep some elements of that:

Suggested change
"'{0}' is a 'get' accessor; did you mean to use it without '()'?": {
"This expression is not callable because it is a 'get' accessor; did you mean to use it without '()'?": {

(You already have an error span so I don't think you need to reproduce the expression in the error -- long expressions result in too-long errors.)

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 9, 2020

Hey @DanielRosenwasser, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/70872/artifacts?artifactName=tgz&fileId=2EE3DBFF97B39DAEC967A7C041F96C371B613531C1CA0C3AE234ED7991E41E6102&fileName=/typescript-3.9.0-insiders.20200409.tgz"
    }
}

and then running npm install.


There is also a playground for this build.

@jtbandes jtbandes force-pushed the diagnose-accidental-accessor-call branch from f92edb7 to d00a5c9 Compare April 11, 2020 23:02
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.

This error should replace "This expression is not callable", or at least be added as an elaboration.

@@ -4384,6 +4384,10 @@
"category": "Error",
"code": 6231
},
"'{0}' is a 'get' accessor; did you mean to use it without '()'?": {
Copy link
Member

Choose a reason for hiding this comment

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

With or without the codefix, I like the () message better since it shows what's wrong instead of naming it in terms that not everyone understands immediately.

@@ -4384,6 +4384,10 @@
"category": "Error",
"code": 6231
},
"'{0}' is a 'get' accessor; did you mean to use it without '()'?": {
Copy link
Member

Choose a reason for hiding this comment

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

re codefixes: There are some simple ones that you can basically copy, then you just have to learn how to extract the property access from the call, which should also be easy.

However, I'd rather just take the error alone and follow up with the codefix.

function test24554(x: Test24554) {
return x.property();
~~~~~~~~
!!! error TS2349: This expression is not callable.
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 much rather replace this message instead of tacking on related info.

If it's too hard to replace the message, it should be an elaboration, not related info. Elaborations provide additional messages; related info is designed to point to a different location.

@@ -4384,6 +4384,10 @@
"category": "Error",
"code": 6231
},
"'{0}' is a 'get' accessor; did you mean to use it without '()'?": {
Copy link
Member

@sandersn sandersn Apr 15, 2020

Choose a reason for hiding this comment

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

Actually, as a replacement for 'this expression is not callable', we should keep some elements of that:

Suggested change
"'{0}' is a 'get' accessor; did you mean to use it without '()'?": {
"This expression is not callable because it is a 'get' accessor; did you mean to use it without '()'?": {

(You already have an error span so I don't think you need to reproduce the expression in the error -- long expressions result in too-long errors.)

@sandersn
Copy link
Member

src/services/codefixes/fixForgottenThisPropertyAccess.ts is a good, simple codefix to start from. But it requires a unique error code, which means replacing "This expression is not callable".

@sandersn sandersn self-assigned this Apr 15, 2020
@sandersn sandersn added the For Backlog Bug PRs that fix a backlog bug label Apr 15, 2020
@sandersn
Copy link
Member

(Sorry, I didn't read your description until just now -- you asked about every single one of the aspects I commented on.)

@jtbandes
Copy link
Contributor Author

Thanks for the feedback!

But it requires a unique error code, which means replacing "This expression is not callable".

That partially answers my question about the best way to make the code fix. But is it really desirable to replace this error message? I guess I'm worried about the rare cases where the remove () suggestion is wrong, and then the new error message wouldn't really make sense. Should we try to also preserve the "not callable" error (the actual "root cause"), or do you really think it's best to replace it?

@jtbandes
Copy link
Contributor Author

I see, I guess starting with "This expression is not callable because…" alleviates my concern. And I suppose we can still keep the "Type {0} has no call signatures" sub-error.

@jtbandes jtbandes force-pushed the diagnose-accidental-accessor-call branch from aa5838c to cd15560 Compare April 19, 2020 06:09
@jtbandes
Copy link
Contributor Author

jtbandes commented Apr 19, 2020

I added a code fix, but to make it easier I modified the parser to save the open/close paren tokens in CallExpression. Is that alright?

Edit: guess it's gonna take a lot more work to fix all the tests 😬

Edit 2: going to remove that from this PR and do it separtely.

@jtbandes
Copy link
Contributor Author

Alright, got the code fix working, except that I had to omit the (/) tokens from forEachChild and change the test harness 😕 : https://github.com/jtbandes/TypeScript/pull/1/files#diff-e4047651bbe0a47423c7db850216a6afR122

@jtbandes jtbandes requested a review from sandersn April 19, 2020 23:17
@jtbandes
Copy link
Contributor Author

Hi @sandersn just a friendly ping on this PR! No rush but I don't want it to languish for too long either :)

!!! error TS2349: This expression is not callable.
!!! error TS2349: Type 'Number' has no call signatures.
!!! message TS6232: This expression is not callable because it is a 'get' accessor. Did you mean to use it without '()'?
!!! related TS2728 tests/cases/conformance/classes/members/classTypes/instancePropertyInClassType.ts:4:13: 'y' is declared here.
Copy link
Member

Choose a reason for hiding this comment

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

I'm not convinced this is valuable. If it is, it should really be on the base "This expression is not callable." error as well. @DanielRosenwasser can you decide on this? I trust your opinion on related spans.

// Diagnose get accessors incorrectly called as functions
const { resolvedSymbol } = getNodeLinks(errorTarget);
if (resolvedSymbol && resolvedSymbol.flags & SymbolFlags.GetAccessor) {
diagnostic = createDiagnosticForNode(errorTarget, Diagnostics.This_expression_is_not_callable_because_it_is_a_get_accessor_Did_you_mean_to_use_it_without);
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 the change should be in invocationErrorDetails, very close to the base "Type_0_has_no_call_signatures", instead of out here in invocationError.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

invocationErrorDetails currently doesn't have enough information (access to the node/symbol) to produce the new error. I could rethink its signature, but that seemed more invasive than just putting it here. I'll take another look later if you think it's better that way!

@jtbandes jtbandes requested a review from sandersn April 30, 2020 06:07
@jtbandes
Copy link
Contributor Author

Alright, I've moved the changes into invocationErrorDetails and removed the related info. (Considered keeping it but the code flow around chains vs related info is already convoluted and I didn't want to make it worse.) Let me know what you think!

@jtbandes
Copy link
Contributor Author

jtbandes commented May 6, 2020

Ping @sandersn 🙌 (Not really in a hurry, I'm sure you are quite busy — just want to avoid falling through the cracks!)

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.

Sorry, noticed one more thing. (This might be the fault of my merge.)

@@ -4416,6 +4416,10 @@
"category": "Error",
"code": 6233
},
"This expression is not callable because it is a 'get' accessor. Did you mean to use it without '()'?": {
"category": "Message",
Copy link
Member

Choose a reason for hiding this comment

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

Should be an Error, not a Message.

@sandersn
Copy link
Member

Travis is all green when I click through to it, so I'm going to go ahead and merge.

@sandersn sandersn merged commit 45cf20c into microsoft:master May 21, 2020
@jtbandes
Copy link
Contributor Author

Great, thanks! I will send out another PR for the code fix in the near future.

@jtbandes jtbandes deleted the diagnose-accidental-accessor-call branch May 23, 2020 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Can we give better error messages on invoked accessors?
5 participants