Skip to content

Fix overriding Java methods, get rid of JavaMethodType #11361

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 5 commits into from
Feb 17, 2021

Conversation

smarter
Copy link
Member

@smarter smarter commented Feb 9, 2021

No description provided.

@smarter
Copy link
Member Author

smarter commented Feb 9, 2021

test performance please

@dottybot
Copy link
Member

dottybot commented Feb 9, 2021

performance test scheduled: 1 job(s) in queue, 0 running.

@dottybot
Copy link
Member

dottybot commented Feb 9, 2021

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/11361/ to see the changes.

Benchmarks is based on merging with master (ef48716)

if prefix eq NoPrefix then
symbol.is(JavaDefined)
else
prefix.classSymbol.is(JavaDefined)
Copy link
Contributor

Choose a reason for hiding this comment

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

What if prefix is an AndType or OrType with some classes that are Java defined?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question, I don't think this was ever handled when merging a JavaMethodType and a MethodType.

  • For override checks, the prefix is always a ThisType where classSymbol delegates to its underlying which could be an AndType if a self-type is present, in that case we should fallback to ThisType#cls instead
  • In other situation, what matter I think is that we respect this law:
    denot.prefix != NoPrefix
               implies
    denot   is equivalent to    denot.prefix.member(denot.name).atSignature(denot.signature)
    
    That is, whatever signature we choose must not be the signature of another overload, so for an intersection of all Java classes we need to pick the Java signature, otherwise we should be able to pick the Scala signature.

I'll add some test cases and tweak the implementation as needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually we know that law is violated already, hence the existence of SELECTin, so as long as we consistently pick one or the other that might be good enough.

symbol.is(Opaque) ||
// SingleDenotation#signature relies on the prefix to compute
// an appropriate signature for methods.
info.isInstanceOf[MethodOrPoly] && symbol.is(JavaDefined) != pre.classSymbol.is(JavaDefined)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does the prefix isJavaDefined status override the symbol's status?

Copy link
Member Author

Choose a reason for hiding this comment

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

When checking if foo in B overrides foo in A, we look at the symbol of foo in A as seen from prefix B.this and compare their signature, so we need to rely on this prefix to know what signature to use to match the signature of the override at its definition site.

@odersky odersky assigned smarter and unassigned odersky Feb 10, 2021
@smarter smarter assigned odersky and unassigned smarter Feb 10, 2021
@smarter
Copy link
Member Author

smarter commented Feb 11, 2021

Thinking about it more, I think it's better to restrict the use of Java signatures to situation where it's actually needed , I'll do another pass tomorrow.

@smarter smarter assigned smarter and unassigned odersky Feb 11, 2021
@smarter smarter force-pushed the drop-javamethod-new-4 branch from 2f002f8 to ecedf60 Compare February 12, 2021 14:33
@smarter
Copy link
Member Author

smarter commented Feb 12, 2021

After trying various approaches, I ended up keeping SingleDenotation#signature simple (use Java signatures if the symbol is Java-defined) which means we don't have to worry about the prefix being set, the trade-off is that SingleDenotation#matchesLoosely has to make sure it's not mixing Java and Scala signatures for override checks to work correctly, cf last commit: ecedf60. I think this is the cleanest solution given the various constraints we have here.

@smarter smarter assigned odersky and unassigned smarter Feb 12, 2021
@smarter smarter force-pushed the drop-javamethod-new-4 branch from ecedf60 to 53fc349 Compare February 15, 2021 23:36
This makes it possible to compute the Java or Scala signature of a
method, regardless of whether it was defined as a JavaMethod or not.
Because there's nothing left that is shareable, this required removing
SignatureCachingType and duplicating the signature caching logic between
MethodOrPoly and NamedType.

This commit itself does not change any semantics, but this new method
will be useful for the next commit.
Before this commit, SingleDenotation#signature simply delegated to
MethodOrPoly#signature which used Java signatures for JavaMethodType and
non-Java signatures otherwise. As a first step towards getting rid of
JavaMethodType, this commit changes this to make
SingleDenotation#signature itself responsible for using Java signatures.
@smarter smarter force-pushed the drop-javamethod-new-4 branch from 53fc349 to 590d587 Compare February 16, 2021 13:56
This is possible thanks to the previous commit, in all remaining places
where we called `isJavaMethod` we can rely on contextual informations to
obtain the same information, so we can completely get rid of it which is
a nice simplification.

Also get rid of ElimRepeated#transformTypeOfTree which turned out to be
a no-op: the denotation transformer already transforms method types and
there's nothing to do on top of that.
Use Jave erasure to compute the result type part of Java method
signatures too. I don't think that two distinct non-bridge Java
overloads can only differ in their result type so this shouldn't fix
anything, but it's more consistent.
To check which methods are in an overriding relationship, we rely on
denotation matching which checks that the methods have matching
signatures, but because Scala and Java have different erasure rules,
seemingly valid overrides might not be seen as such because they end up
with different signatures. To compensate for this we change
SingleDenotation#matchesLoosely to use the Scala signatures of both
methods when comparing a Scala and a Java method. This works even though
the methods end up with different erased types because Erasure will
insert bridges.

Note that even after this change, we cannot simply override a Java
generic `Array[T]` with a Scala `Array[T]`, because we intentionally
parse these Java types as `Array[T & Object]`, see
`TypeApplications.translateJavaArrayElementType` for details and
tests/{neg,pos}/i1747.scala for examples.
case tp =>
tp

def transformTypeOfTree(tree: Tree)(using Context): Tree =
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a rationale (except tests pass) that we can remove these? I mean, it's great that we can, but I wonder why the overrides were there in the first place.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that the TimeTravellingTreeCopier (which didn't exist back when ElimRepeated was first added) takes care of any necessary type update in tree.

@odersky odersky assigned odersky and unassigned odersky Feb 16, 2021
@smarter smarter merged commit 8bbb0ba into scala:master Feb 17, 2021
@smarter smarter deleted the drop-javamethod-new-4 branch February 17, 2021 12:00
smarter added a commit to dotty-staging/dotty that referenced this pull request Feb 17, 2021
This was already fixed by scala#11361.
smarter added a commit to dotty-staging/dotty that referenced this pull request Feb 17, 2021
This was already fixed by scala#11361.
smarter added a commit to dotty-staging/dotty that referenced this pull request Feb 17, 2021
This was already fixed by scala#11361.
smarter added a commit to dotty-staging/dotty that referenced this pull request Feb 23, 2021
This was already fixed by scala#11361.
@Kordyjan Kordyjan added this to the 3.0.0 milestone Aug 2, 2023
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.

4 participants