Skip to content

SR-13976: Improve compiler error message: "partial application of ‘mutating’ method is not allowed" #35466

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 1 commit into from
Oct 7, 2021

Conversation

abdulajet
Copy link

@abdulajet abdulajet commented Jan 17, 2021

As mentioned on the Jira ticket, partial application is not a well-known term so I have reworded the error messages with a suggested phrase to help people diagnose the issue easier.

Based on the diagnostics definition file there were 4 different combinations that I saw that used the error, below is the wording changes i made:

  • partial application of 'mutating' method is not allowed -> calling a 'mutating' method without an argument list is not allowed
  • partial application of 'super.init' initializer chain is not allowed -> calling a 'super.init' initializer chain without an argument list is not allowed
  • partial application of 'self.init' initializer delegation is not allowed -> calling a 'self.init' initializer delegation without an argument list is not allowed
  • partial application of 'super' instance method with metatype base is not allowed -> calling a 'super' instance method with metatype base without an argument list is not allowed

The first commit has the change, second commit has the test updates.

Resolves SR-13976. cc @varungandhi-apple

@varungandhi-apple
Copy link
Contributor

varungandhi-apple commented Jan 19, 2021

Thanks for the PR. 🙂

Minor comment: Generally we try to implement the test and change in the same commit (or alternately, add a failing test marked as XFAIL in the first commit, and implement the fix and remove the XFAIL in the second commit). That way, bisection becomes easier. For this particular case, it's not a big deal though, as we can squash the changes at the end.

@abdulajet
Copy link
Author

I can squash the commits when I change from calling -> using

@abdulajet
Copy link
Author

This is what I have come up with! What do you folks think before I go further? @varungandhi-apple @LucianoPAlmeida @xedin

 if (auto FD = dyn_cast<FuncDecl>(getDC())) {
    if (FD->getParameters()->size() == 0) {
      emitDiagnosticAt(getLoc(), diag::add_empty_parameter_list_fixit,
                       anchor->getName().getBaseIdentifier())
          .fixItInsertAfter(getLoc(), "()");
      }
  }

Here is some of the test output from Constraints/mutating_members.swift

/Users/aajetunmobi/Documents/swiftproject/swift/test/Constraints/mutating_members.swift:9:37: error: incorrect message found
let x = Foo.boom // expected-error{{partial application of 'mutating' method}}
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                   cannot reference 'mutating' method as a function value
/Users/aajetunmobi/Documents/swiftproject/swift/test/Constraints/mutating_members.swift:11:36: error: incorrect message found
let z0 = y.boom // expected-error{{partial application of 'mutating' method}}
                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                  cannot reference 'mutating' method as a function value
/Users/aajetunmobi/Documents/swiftproject/swift/test/Constraints/mutating_members.swift:12:42: error: incorrect message found
let z1 = Foo.boom(&y) // expected-error{{partial application of 'mutating' method}}
                                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                        cannot reference 'mutating' method as a function value
/Users/aajetunmobi/Documents/swiftproject/swift/test/Constraints/mutating_members.swift:15:10: error: unexpected note produced: did you mean to call 'boom' with '()'?
 return Foo.boom // expected-error{{partial application of 'mutating' method}}

@xedin
Copy link
Contributor

xedin commented Jan 29, 2021

I like it! I'd also suggest to add a note - did you mean to call it with '()' and attach a fix-it to that instead of fix-it alone, it make it more clearer.

@xedin
Copy link
Contributor

xedin commented Jan 29, 2021

And one more thing - a better check would be to account for defaultable parameters as well, so if there are 0 parameters or all of the parameters are defaultable - fix-it.

@abdulajet
Copy link
Author

I like it! I'd also suggest to add a note - did you mean to call it with '()' and attach a fix-it to that instead of fix-it alone, it make it more clearer.

I thought that was the purpose of the add_empty_parameter_list_fixit diagnostic? The last error has an additonal error did you mean to call 'boom' with '()'? @xedin

@xedin
Copy link
Contributor

xedin commented Feb 1, 2021

Ah, right, sorry I missed that one, then the only viable thing is related to default parameters and we should be good to go!

@abdulajet
Copy link
Author

Ah, right, sorry I missed that one, then the only viable thing is related to default parameters and we should be good to go!

I cannot figure out how to check for that. Is there a part of the docs I should be looking at specifically?

@xedin
Copy link
Contributor

xedin commented Feb 1, 2021

If I remember correctly parameter list consists of ParamDecl * and there is a way to check whether it has a default or not, so you'd just need add a check to make sure that if there are parameters they are all defaulted.

@LucianoPAlmeida
Copy link
Contributor

I cannot figure out how to check for that. Is there a part of the docs I should be looking at specifically?

An example you can look at :) https://github.com/apple/swift/blob/19a4df331c3de41dac1b2d069cbda863ed0f24e5/lib/Sema/CSSimplify.cpp#L3437-L3446

@abdulajet
Copy link
Author

Sorry for the delay folks. The example posted earlier has some functions that aren't available in the file, as to not add more imports I went down the ParamDecl route.

  if (auto FD = dyn_cast<FuncDecl>(getDC())) {
    auto params = FD->getParameters();

    bool hasNonDefaultParam =
        std::any_of(params->begin(), params->end(),
                    [](ParamDecl *param) { return !param->isDefaultArgument(); });

    if (params->size() == 0 && !hasNonDefaultParam) {
      emitDiagnosticAt(getLoc(), diag::add_empty_parameter_list_fixit,
                       anchor->getName().getBaseIdentifier())
          .fixItInsertAfter(getLoc(), "()");
    }
  }

Btw is it ok to force push over this branch?

@xedin
Copy link
Contributor

xedin commented Feb 5, 2021

Yeah, it's fine to force push.

@xedin
Copy link
Contributor

xedin commented Feb 5, 2021

@abdulajet There is an llvm equivalent of std::any_of - it works with ArrayRefs so you don't have to use begin/end e.g. llvm::any_of(FD->getParameters(), [](const ParamDecl *param) { ... })

@xedin
Copy link
Contributor

xedin commented Feb 5, 2021

I would suggest using llvm::none_of here though to avoid negative checks.

@varungandhi-apple
Copy link
Contributor

Also, please don't apologize about delays or similar. 😄 It's totally okay. You're doing this in your free time and we are thankful for your contributions, at whatever pace works for you.

@abdulajet
Copy link
Author

Thanks for all the help!

 if (auto FD = dyn_cast<FuncDecl>(getDC())) {
    auto params = FD->getParameters();

    bool hasNoNonDefaultParams = llvm::none_of(
        *params, [](ParamDecl *param) { return param->isDefaultArgument(); });

    if (params->size() == 0 && hasNoNonDefaultParams) {
      emitDiagnosticAt(getLoc(), diag::add_empty_parameter_list_fixit,
                       anchor->getName().getBaseIdentifier())
          .fixItInsertAfter(getLoc(), "()");
    }
  }

Ill get the tests done then push this up.

@xedin
Copy link
Contributor

xedin commented Feb 5, 2021

Sounds great, thank you!

@xedin
Copy link
Contributor

xedin commented Feb 5, 2021

if (params->size() == 0 && hasNoNonDefaultParams) {

I think you mean ||

@abdulajet
Copy link
Author

aha yes i 100% do. cheers

@LucianoPAlmeida
Copy link
Contributor

Sorry if this is being nitpicking but I think that llvm::all_of would be more readable here
Because (params->size() == 0 || hasNoNonDefaultParams) reads has no params or has no non default params
But (params->size() == 0 || allParamsHaveDefaultArg) reads has no params or all parameters have a default argument which reads as exactly what is the intention here without double no. WDYT @xedin?
Both are fine but the first one took me more time to understand ...

@xedin
Copy link
Contributor

xedin commented Feb 6, 2021

Works either way, I think it's a matter of personal preference.

@abdulajet
Copy link
Author

abdulajet commented Feb 12, 2021

I have an issue where the conditions for the fixit are being applied where they shouldn't:

super.baseFunc0() in NameLookup/name_lookup.swift is passing the condition of no params. So i think we also need to check if the function isnt being called already:

if ((params->size() == 0 && allParamsHaveDefaultArgs) && notBeingCalled) {

}

Is there someone I can see an example of a check for if the function is being called? Not sure i can see anything on the public interface of FuncDecl or UnresolvedDotExpr

For @xedin you mentioned

Please add a test case or two for the fix-it as well

in the initial discussion. Should this be in its own test file?

Thanks all!

@xedin
Copy link
Contributor

xedin commented Feb 16, 2021

So i think we also need to check if the function isnt being called already:

Shouldn't the logic which detects that it's a partial application already handle that?

Should this be in its own test file?

I think you can add new tests to test/Constraints/members.swift.

@abdulajet
Copy link
Author

Shouldn't the logic which detects that it's a partial application already handle that?

Oh yeah, of course! In that case, I am not sure why the diag is being called for this test

Here is an example of the failing test from NameLookup/name_lookup.swift

super.baseFunc0() // expected-error {{instance member 'baseFunc0' cannot be used on type 'ThisBase1'}}
// expected-error@-1 {{cannot reference 'super' instance method with metatype base as a function value}}

@xedin
Copy link
Contributor

xedin commented Feb 16, 2021

@abdulajet Since baseFunc0 is an instance method its signature is (Self) -> () -> Void, I think the diagnostic logic is wrong and it shouldn't even talk about partial application in this case since it's used from a class func context so super there means a different thing.

@abdulajet
Copy link
Author

abdulajet commented Feb 17, 2021

Cool, ill look into that. Thanks

@abdulajet
Copy link
Author

abdulajet commented Feb 19, 2021

@xedin I have had a look around, the path I was going down was trying to see if the function was marked with a class descriptor and returning false out of the diagnostic. Would this a good solution (if it is, can I have a hand in checking the function descriptors. getDescriptiveKind on Decl didn't seem to work) or would it be better to go further upstream and stop the PartialApplicationFailure::diagnoseAsError() function from being called for a class func entirely.

Thanks again

@xedin
Copy link
Contributor

xedin commented Feb 23, 2021

@abdulajet The way diagnostics are currently implemented in the solver is two-fold - there is a "ConstraintFix" and that has a "FailureDiagnostic" attached to that. ConstraintFix subtypes are responsible for failure identification and information storage, FailureDiagnostic subtypes are responsible for diagnostic emission based on collected information.

What you want to do here is to prevent a fix (AllowInvalidPartialApplication in this case) from being recorded in situations like what you have described. There is a method which is responsible for verifying references - isInvalidPartialApplication (it's called by ConstraintSystem::resolveOverload), you'd need to adjust isInvalidPartialApplication to consider declaration context associated with given constraint system (accessible via DC member of ConstraintSystem) before making decisions about invalid partial applications.

@abdulajet
Copy link
Author

Amazing! Will take a look, thanks.

@LucianoPAlmeida
Copy link
Contributor

Should we scope this down to message adjustments? I think changing it to say referencing ... as a function value should be landable by itself and all of the additional changes could be done in a separate PR.

Given that the SR is only about the terminology, I think this would be fine :)
Perhaps we could create another SR for the additional issues? It would also be easier to describe the additional issues specifically there to narrow that from this thread since it got a lot of things(e.g. implementation details, crash logs and so on...) and is a bit hard to narrow it down.
Maybe it would also be good to add a FIXME(diagnostics) comment around the problematic case as well(as far as I remenber the additional issue is about a specific edge case)? What you think @xedin?

@xedin
Copy link
Contributor

xedin commented Sep 14, 2021

I think using FIXME(diagnostics) would be fine as soon as we have all of the issues filed as SRs so it's easy to keep track of them and tackle one by one.

@abdulajet
Copy link
Author

ok to revert back to just changing the text of the diagnostic then 2 new SRs. One for the diagnostic being called incorrectly and a dependent one that adds the fix it?

@LucianoPAlmeida
Copy link
Contributor

ok to revert back to just changing the text of the diagnostic then 2 new SRs. One for the diagnostic being called incorrectly and a dependent one that adds the fix it?

Sounds good to me =]

@abdulajet
Copy link
Author

SRs:
https://bugs.swift.org/browse/SR-15250
https://bugs.swift.org/browse/SR-15251

@LucianoPAlmeida
Copy link
Contributor

SRs:
https://bugs.swift.org/browse/SR-15250
https://bugs.swift.org/browse/SR-15251

Thank you!
For this one then we only need to rebase with man, make sure all tests pass, add the proper TODO(diagnostics) referencing the SRs and this would be good to go I think

@abdulajet
Copy link
Author

Sure thing! Will do it this weekend

@abdulajet
Copy link
Author

Did we want to leave it as "calling" or change to "referencing"

@LucianoPAlmeida
Copy link
Contributor

Did we want to leave it as "calling" or change to "referencing"

From what I see here #35466 (comment) we agreed on cannot reference 'mutating' method "foo" as a function value

@xedin
Copy link
Contributor

xedin commented Oct 1, 2021

Agree with @LucianoPAlmeida here, "referencing" seems more appropriate here.

@abdulajet
Copy link
Author

@LucianoPAlmeida updated to use "reference" and added some comments to name_lookup.swift for the linked SRs. Let me know if they are better placed elsewhere

@LucianoPAlmeida
Copy link
Contributor

@swift-ci Please smoke test

Copy link
Contributor

@LucianoPAlmeida LucianoPAlmeida 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 to go to me!

@LucianoPAlmeida LucianoPAlmeida requested a review from xedin October 3, 2021 15:52
@LucianoPAlmeida
Copy link
Contributor

@abdulajet there is one missing test to adjust /test/type/self.swift:358

"'mutating' method|"
"'super.init' initializer chain|"
"'self.init' initializer delegation|"
"'super' instance method with metatype base"
"}0 is not allowed",
"}0 without an argument list",
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought that we agreed to use as function value instead of without an argument list? Also nit on the grammar - diagnostics shouldn't include articles "a" or "an" in the text because they make a diagnostic longer without adding any information (see https://github.com/apple/swift/blob/main/docs/Diagnostics.md).

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yeah, that's true, I just miss that 😅

Copy link
Author

Choose a reason for hiding this comment

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

my bad. referencing x as a function value coming up

@LucianoPAlmeida
Copy link
Contributor

@swift-ci Please smoke test

"'mutating' method|"
"'super.init' initializer chain|"
"'self.init' initializer delegation|"
"'super' instance method with metatype base"
"}0 is not allowed; calling the function has undefined behavior and will "
"}0 as function value; calling the function has undefined behavior and will "
Copy link
Author

@abdulajet abdulajet Oct 6, 2021

Choose a reason for hiding this comment

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

I cannot build swift locally at the moment, while I fix that so I can run the test suite locally. Does the second clause here make sense? "cannot reference x as function value calling the function has undefined..."

I think so since is error/warning will be in the context of a function I believe

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think it makes sense, second part is a clarification on why it can't be used as a function value - because calling it is UB.

@@ -6,31 +6,31 @@ struct Foo {
mutating func boom() {}
}

let x = Foo.boom // expected-warning{{partial application of 'mutating' method}}
let x = Foo.boom // expected-warning{{cannot reference 'mutating' method as function value; calling the function has undefined behavior and will be an error in future Swift versions}}
Copy link
Author

Choose a reason for hiding this comment

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

I am not sure why this test was working on main before? the warnings for swift 4 should have always had the second clause.

But alas, run the tests locally and fixed the CI issues

@xedin
Copy link
Contributor

xedin commented Oct 6, 2021

@swift-ci please test

@xedin
Copy link
Contributor

xedin commented Oct 7, 2021

Looks like it's time to land this! Thank you, @abdulajet and @LucianoPAlmeida!

@xedin xedin merged commit 6b2c016 into swiftlang:main Oct 7, 2021
@abdulajet
Copy link
Author

thanks all for the help!

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