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

Warn if extension receiver already has member #17543

Merged
merged 2 commits into from
Apr 9, 2024

Conversation

som-snytt
Copy link
Contributor

@som-snytt som-snytt commented May 19, 2023

Best effort to warn if defining extension method for a type which has a non-private member of the same name and a subsuming signature.

Excludes opaque types (for defining forwarders to a public API) and other alias types (where the type member may override an abstract type member).

Fixes #16743

@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch 3 times, most recently from 2041485 to c521dae Compare May 20, 2023 03:16
@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch from c521dae to e9ab309 Compare June 1, 2023 20:33
@som-snytt som-snytt marked this pull request as ready for review June 1, 2023 20:34
@sjrd
Copy link
Member

sjrd commented Jun 1, 2023

@jchyb ping for triage. IIUC you're issue supervisor this week?

@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch from e9ab309 to 35f27ec Compare June 2, 2023 06:07
@jchyb jchyb requested a review from smarter June 2, 2023 10:41
@jchyb jchyb assigned smarter and unassigned jchyb Jun 2, 2023
@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch from 35f27ec to 6303e64 Compare January 4, 2024 22:35
Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

Oops, looks like I completely missed this. I think this is fine as-is since it shouldn't have any false positives but I left a comment with a possible improvement to avoid false negatives.

Comment on lines 2603 to 2604
val other = paramTpt.tpe.nonPrivateMember(name)
if other.exists && sym.denot.info.resultType.matches(other.info) then
Copy link
Member

@smarter smarter Jan 4, 2024

Choose a reason for hiding this comment

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

There's a few situations where this check wouldn't be enough:

  1. The method defined in the class is overloaded (other.exists will return false).

  2. The parameters in the extension definition are subtypes of the one in the primary definition:

class C { def foo(x: Any) = 0 }
extension (c: C) def foo(x: Int) = 1 // uncallable
  1. Only the first (if any) parameter list matches:
class C { def foo(x: Int)(y: String) = 0 }
extension (c: C) def foo(x: Int)(y: Int) = 1 // uncallable
class C { def foo: Int = 0 }
extension (c: C) def foo: String = "" // uncallable
  1. The primary definition might have a type parameter or implicit/using clause:
class C { def foo(using Int) = 0 }
extension (c: C) def foo = 1 // uncallable
  1. The extension definition might have a type parameter or implicit/using clause in its prefix:
class C { def foo = 0 }
extension (using Int)(c: C) def foo = 1 // uncallable
  1. Borderline (which we may or may not want to warn about): the extension definition might have a type parameter or implicit/using clause in its suffix:
class C { def foo = 0 }
extension (c: C) def foo(using Int) = 1 // only callable by explicitly passing `(using ...)`

I think this is solvable by doing something like (using Applications.stripImplicit):

Suggested change
val other = paramTpt.tpe.nonPrivateMember(name)
if other.exists && sym.denot.info.resultType.matches(other.info) then
def firstExplicitParamTypes(tp: Type) = stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes
val extFirst = firstExplicitParamTypes(sym.denot.info)
val matching =
paramTpt.tpe.nonPrivateMember(name)
.filterWithPredicate: clsMeth =>
val clsFirst = firstExplicitParamTypes(clsMeth.info)
extFirst.lazyZip(clsFirst).forall(_ frozen_<:< _)
.nonEmpty

But I haven't actually tried it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I was bored and in need of stimulation. If I'm able to work through the cases, I'm sorry in advance that I will request your re-review.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

case 7 is case 6 but where c.foo(using x) typechecks with native member returning { def apply(using X) }. I had to refresh my knowledge, and the reference is not current or precise (as it mentions only when m is missing, not when m is present but expression doesn't typecheck). I'll experiment now with your code suggestion.

@smarter smarter assigned som-snytt and unassigned smarter Jan 4, 2024
@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch from 6303e64 to 46d6a58 Compare January 11, 2024 16:16
@som-snytt
Copy link
Contributor Author

Ha, it errored on an old line of mine. The test almost covered it: member m: String hides extension m(i: Int) because m(42) is String#apply, whereas m: Int does not hide.

Result type does not drive extension search, so m: String always hides nullary extension.

For non-nullary extension and nullary member, consider apply in result.

The existing test case n was only about nullary member and extension with implicit params. We need a word, "implicitary", for that.

@som-snytt
Copy link
Contributor Author

som-snytt commented Jan 11, 2024

Moved the check to refchecks, where it is more at home.

Added some test code and conditions, learned something about extension methods and something about dotty internals!

Edit: previously, assumed incorrectly that all scaladoc test failures were spurious.

Error:  -- [E193] Potential Issue Error: /home/runner/work/dotty/dotty/scaladoc-testcases/src/tests/implicitConversions.scala:9:21 
Error:  9 |extension (a: A) def extended_bar(): String = ???
Error:    |                     ^
Error:    |                Suspicious extension extended_bar is already a member of A
Error:    |
Error:    | longer explanation available when compiling with `-explain`
Error:  one error found

where there is an extension method of the same name on a different type in A. I slapped a nowarn on it.

I was hoping to get out for some noontime exercise, but local dotty says you can't annotate an extension! I'm learning so much about extensions today! I guess the annotation goes on the def.

@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch 2 times, most recently from 97bf8a3 to badd133 Compare January 11, 2024 21:11
@som-snytt
Copy link
Contributor Author

Already missed the narrow window to claim that message ID.

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM.

compiler/src/dotty/tools/dotc/typer/RefChecks.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/typer/RefChecks.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/typer/RefChecks.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/typer/RefChecks.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/reporting/messages.scala Outdated Show resolved Hide resolved
@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch 4 times, most recently from d947fbf to db578b5 Compare March 21, 2024 07:19
@som-snytt
Copy link
Contributor Author

Followed up on previous review.

@som-snytt som-snytt removed their assignment Mar 21, 2024
Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM, thanks for your work!

compiler/src/dotty/tools/dotc/reporting/messages.scala Outdated Show resolved Hide resolved
@som-snytt som-snytt force-pushed the issue/16743-dubious-extension branch from 80d7a03 to fdb33a8 Compare April 4, 2024 23:29
@som-snytt som-snytt requested a review from smarter April 9, 2024 17:52
@som-snytt
Copy link
Contributor Author

Last chance for this PR to grab an error ID before other PRs overtake it. #20143

@smarter smarter merged commit 939f73e into scala:main Apr 9, 2024
19 checks passed
@smarter
Copy link
Member

smarter commented Apr 9, 2024

🥇

@som-snytt som-snytt deleted the issue/16743-dubious-extension branch April 9, 2024 18:47
@Kordyjan Kordyjan added this to the 3.5.0 milestone May 10, 2024
WojciechMazur added a commit that referenced this pull request Jul 5, 2024
Backports #17543 to the LTS branch.

PR submitted by the release tooling.
[skip ci]
@soronpo
Copy link
Contributor

soronpo commented Aug 11, 2024

This PR creates false positives under 3.5.0-RC7. Need to minimize.

@soronpo
Copy link
Contributor

soronpo commented Aug 11, 2024

I'm sorry, I phrased it incorrectly. These are not false positives, but they create warnings for definitions I use as workaround for lack of imports in an extension method block.

Here is an example:

class Foo(val someField: Int)

extension (foo: Foo)
  def someField = foo.someField
  def bar1 = 1 + someField
  def bar2 = 2 + someField
  def bar3 = 3 + someField

Since I cannot apply import foo.someField commonly in the extension method block, this concept has been a useful hack.
@som-snytt What do you think about excluding the specific pattern of def <fieldName> = extended.<fieldName> from the warning?

Or alternatively, should we just enable the original extended value fields within each of the extension method's scope?

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.

Java additions can silently break overloading extension methods
6 participants