Skip to content

Conversation

@jvican
Copy link
Member

@jvican jvican commented Sep 2, 2017

Java type selections, for example Foo.Bar, can represent two different
trees depending on the context. On the one hand, it can be a type
selection over an object Foo. On the other hand, it can be a type
selection over a type (that is, a selection of a nested class).

However, when we parse the Scala signatures is not possible to know
which case the user meant. For example, Foo.Bar could be either:

  1. Select(Ident(TermName("Foo")), TypeName("Bar")); or
  2. SelectFromTypeTree(Ident(TypeName("Foo")), TypeName("Bar")).

By default, the Scala compiler parses Foo.Bar in a type position as
the first option, but it could very well be the case that the user meant
the second one, as specified by the tests in this commit.

To address the issue, then, there's nothing but the ugly solution of
injecting special logic into typer so that we try to typecheck both
trees for those different scenarios.

To handle this specific logic and try to make it as isolated as
possible, I've introduced a new context mode called JavaTypeSelection.
This context mode is set by typedSelectOrSuperCall when the
compilation unit is Java, the tree at hands selects a type name and the
qualifier is an identifier. typedIdent detects when this mode is on,
and if the default term symbol lookup fails it tries to find the type
symbol. If that succeeds, it then delegates to typedType. The
different returning trees are handled in typedSelectOrSuperCall.

Run tests in this commit illustrate both accesses. They are independent
because if t10490 had the Foo companion, the bug that this commit
fixes wouldn't have been required (i.e. it's a very subtle workaround).

@scala-jenkins scala-jenkins added this to the 2.12.4 milestone Sep 2, 2017
qual match {
case id: Ident if context.unit.isJava =>
val qualTyped1 = context.withinJavaTypeSelection(typeQual)
(qualTyped1, qualTyped1.isType)
Copy link
Member Author

Choose a reason for hiding this comment

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

It's really interesting that this is all we need to do in typedSelectOrSuperCall, since the whole method will return Select(qualTyped1, name) whereas the more correct tree would have been SelectFromTypeTree(qualTyped1, name). I have not done this change because of simplicity. But it does make me wonder what is the crucial difference between Select and SelectFromTypeTree... It seems that Select(Ident(TypeName("Foo")), _) == SelectFromTypeTree(Ident(TypeName("Foo")), _), no?

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 for consistency it would be better to generate the SelectFromTypeTree, even if a Select doesn't violate any of the current assumptions in the rest of the compiler.

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 can do that, but I'm afraid to modify too much of the logic of this method since it's really hot. I also think that for consistency it's better to generate a SelectFromTypeTree. Doing that requires me to wrap almost all the rest of the body that follows this statement with an if.

Copy link
Contributor

Choose a reason for hiding this comment

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

This should also get a comment that explains why context.unit.isJava here needs to be reified as a context.withinJavaTypeSelection for consumption down the line in typedIdent.

qual match {
case id: Ident if context.unit.isJava =>
val qualTyped1 = context.withinJavaTypeSelection(typeQual)
(qualTyped1, qualTyped1.isType)
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 for consistency it would be better to generate the SelectFromTypeTree, even if a Select doesn't violate any of the current assumptions in the rest of the compiler.

if (name.isTermName) (typeQual, false)
else {
qual match {
case id: Ident if context.unit.isJava =>
Copy link
Member

Choose a reason for hiding this comment

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

A comment would be good here, e.g.

scala/bug#10490: given a Scala class A { class B }, in mixed compilation, a reference A.B in a Java source
would cause a "not found: value A". The `JavaTypeSelection` mode makes `typedIdent` type check A as
a type if no value is found. In case A has a companion, it works thanks to the fix of scala/bug#3120 in
typedSelectInternal.

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'll add it 👍

@retronym
Copy link
Member

retronym commented Sep 4, 2017

It would be worth looking at #5762, which overhauls typechecking of selections to remove inconsistencies. I wasn't brave enough to merge that into 2.12.3, but will likely be merged soon.

@smarter
Copy link
Member

smarter commented Sep 10, 2017

To address the issue, then, there's nothing but the ugly solution of
injecting special logic into typer so that we try to typecheck both
trees for those different scenarios.

Isn't there already logic in the typer to do this in

if (context.unit.isJava && name.isTypeName) {
// scala/bug#3120 Java uses the same syntax, A.B, to express selection from the
// value A and from the type A. We have to try both.
atPos(tree.pos)(gen.convertToSelectFromType(qual, name)) match {
case EmptyTree => None
case tree1 => Some(typed1(tree1, mode, pt))
}
}
? Dotty does something very much inspired by that (https://github.com/lampepfl/dotty/blob/851c3c2212b7457dedc9ad1e59ff688b17458db5/compiler/src/dotty/tools/dotc/typer/Typer.scala#L418-L421) and both of your test cases compile with it.

@jvican
Copy link
Member Author

jvican commented Sep 10, 2017

I saw that logic, I don't know why it was added but it doesn't seem to work; this path in typedSelect is never hit because it is typedIdent the one that fails to typecheck Ident(TypeName("foo")). An error is registered right away in the reporter and the symbol and name are marked as errors when they hit typedSelect.

I would also argue that aside from working, my fix is more elegant and correct. The real fix should happen in typedIdent, not typedSelect, and typedSelect should not try to reverse errors that are reported by typedIdent. The less communication and trial and error, the better IMO.

@jvican
Copy link
Member Author

jvican commented Sep 10, 2017

@retronym I've read the code in that PR. It doesn't seem to interact with the bug that I fix here, let me know if I'm wrong. I'm happy to rebase this when that gets merged.

@lrytz
Copy link
Member

lrytz commented Sep 11, 2017

The existing logic in typedSelect is for a selection A.B in a Java source file, where A and B are java-defined classes.

class A { class B { } }        // v1
class A { static class B { } } // v2
class C { void foo(A.B) { } }

For each package-level Java class, the Scala compiler creates a class and a module symbol, static members are added to the module symbol.

When the Scala compiler type-checks A.B, it doesn't know if B is a static class or not. That's what the existing fix is for. (If A.B was written in Scala, we'd now it's the static member, as the non-static one would be A#B.)

The fix in this PR is when selecting A.B in a Java source with A defined in Scala. A is type checked as a term ident. This fails if A doesn't have a companion object, as there's no value A, only a type.

So there must be some other code path than https://github.com/lampepfl/dotty/blob/851c3c2212b7457dedc9ad1e59ff688b17458db5/compiler/src/dotty/tools/dotc/typer/Typer.scala#L418-L421 in dotty that makes the example work.

@jvican
Copy link
Member Author

jvican commented Sep 19, 2017

@adriaanm Any chance this can be merged for 2.12.4? I'll update it as soon as #5762 lands...

@adriaanm
Copy link
Contributor

I don't see why not. I'm making my way through the PR queue. Will look at this one next (in the next few hours).

@adriaanm
Copy link
Contributor

I assume you meant whether it can be merged once the review feedback has been addressed? I +1 the more comments as requested by Lukas -- this is tricky stuff and there's some good knowledge around this in this PR that should end up in the code.

I'll look at the actual code after lunch, but I would also second the other request in directly emitting the right tree (SelectFromType), if we can find an elegant way to do it.

@adriaanm
Copy link
Contributor

When you rebase, please also drop "Fix scala/bug#10490: " from the commit title. That should go into the PR description. Thanks for the great commit message.

@adriaanm
Copy link
Contributor

adriaanm commented Sep 20, 2017

I've pushed a tree with a sketch that rebases your work on my earlier PR and also boyscouts a bit in the area. Could you verify that it makes sense and absorb this into your PR? Now that I've looked at the code, I feel this area has hit a critical point where we need to do a bit of reinforcement before we tack on more stuff.

@jvican
Copy link
Member Author

jvican commented Sep 21, 2017

I assume you meant whether it can be merged once the review feedback has been addressed?

Indeed.

When you rebase, please also drop "Fix scala/bug#10490: " from the commit title. That should go into the PR description. Thanks for the great commit message.

Deal.

I've pushed a tree with a sketch that rebases your work on my earlier PR and also boyscouts a bit in the area. Could you verify that it makes sense and absorb this into your PR? Now that I've looked at the code, I feel this area has hit a critical point where we need to do a bit of reinforcement before we tack on more stuff.

Excellent. I'm absorbing these commits into this PR.

@jvican
Copy link
Member Author

jvican commented Sep 25, 2017

This PR has been updated according to feedback and is ready for another pass. I haven't scheduled benchmarks because I'm not sure this PR requires them!

@jvican
Copy link
Member Author

jvican commented Sep 25, 2017

/rebuild

@jvican
Copy link
Member Author

jvican commented Sep 25, 2017

Weird -- I cannot reproduce the error locally. I'm trying tomorrow again.

@adriaanm
Copy link
Contributor

I'll take a look as well

@adriaanm
Copy link
Contributor

adriaanm commented Sep 25, 2017

I think we should just update the check file. It doesn't make sense to me to expand to selectDynamic("<error>") when we encounter a mall-formed tree. This is why I pulled that condition out as if (name == nme.ERROR || qual.tpe.widen.isErroneous) EmptyTree.

adriaanm and others added 2 commits September 26, 2017 00:00
Java type selections, for example `Foo.Bar`, can represent two different
trees depending on the context. On the one hand, it can be a type
selection over an object `Foo`. On the other hand, it can be a type
selection over a type (that is, a selection of a nested class).

However, when we parse the Scala signatures is not possible to know
which case the user meant. For example, `Foo.Bar` could be either:

1. `Select(Ident(TermName("Foo")), TypeName("Bar"))`; or
2. `SelectFromTypeTree(Ident(TypeName("Foo")), TypeName("Bar"))`.

By default, the Scala compiler parses `Foo.Bar` in a type position as
the first option, but it could very well be the case that the user meant
the second one, as specified by the tests in this commit.

To address the issue, then, there's nothing but the ugly solution of
injecting special logic into typer so that we try to typecheck both
trees for those different scenarios.

To handle this specific logic and try to make it as isolated as
possible, I've introduced a new context mode called `JavaTypeSelection`.
This context mode is set by `typedSelectOrSuperCall` when the
compilation unit is Java, the tree at hands select a type name and the
qualifier is an identifier. `typedIdent` detects when this mode is on,
and if the default term symbol lookup fails it tries to find the type
symbol. If that succeeds, it then delegates to `typedType`. The
different returning trees are handled in `typedSelectOrSuperCall`.

Run tests in this commit illustrate both accesses. They are independent
because if t10490 had the `Foo` companion, the bug that this commit
fixes wouldn't have been required (i.e. it's a very subtle workaround).

Fixes scala/bug#10490.
@jvican
Copy link
Member Author

jvican commented Sep 25, 2017

@adriaanm Makes sense. PR has been updated with the updated check file, agreed that the new error message is better than exposing the last try with the dynamic call typechecking.

@lrytz
Copy link
Member

lrytz commented Sep 26, 2017

@jvican
Copy link
Member Author

jvican commented Sep 26, 2017

I'd like to see a summary of #6053 (comment) in the commit message.

I'll add it.

Adriaan's comment #6053 (comment) was not addressed, I agree we should document why a mode is necessary

Sorry, I missed this one.

I also proposed a comment here: #6053 (comment), I don't see it added.

It's been added, but it's shorter:
https://github.com/scala/scala/pull/6053/files#diff-4eab1aad4533a31c10565971e90f73eaR5047

@adriaanm
Copy link
Contributor

We're still building a Select tree instead of a SelectFromTypeTree. I'd like to see that changed, @adriaanm wdyt?

yes please -- let's stick to the convention (although the original design decision is not immediately clear to me)

def typedTypeSelectionQualifier(tree: Tree, pt: Type = AnyRefTpe) = {
def typedQual = context.withImplicitsDisabled { typed(tree, MonoQualifierModes | mode.onlyTypePat, pt) }
tree match {
case id: Ident if context.unit.isJava => context.withinJavaTypeSelection { typedQual }
Copy link
Contributor

Choose a reason for hiding this comment

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

Talking this through with @retronym, it occurred to us that this withinJavaTypeSelection context bit would also be useful in @hrhino's quest to improve error messages in #6042. With that insight, should it just be withinTypeSelection (and should we have one for withinPattern?)

Also, why is this is only done when we reach an Ident? What are the varying combos of A, A.B and A.B.C.

Copy link
Member

Choose a reason for hiding this comment

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

why is this is only done when we reach an Ident

For selections there's an existing fix / workaround, see my comment here #6053 (comment).

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be feasible to combine the two fixes?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I think so.

Copy link
Member Author

Choose a reason for hiding this comment

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

@adriaanm i was having a look at rebasing this PR and addressing your comment. Do you want me to combine the two fixes on this PR or how should we operate? /cc @hrhino

Copy link
Contributor

Choose a reason for hiding this comment

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

combining sounds good to me

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright, I'll cherry-pick @hrhino's changes into this PR 👍

@jvican
Copy link
Member Author

jvican commented Sep 27, 2017

@adriaanm Let's delay this change to 2.12.5. I'll update this PR and try to integrate it with other efforts in this area, as you've pointed out to @hrhino's efforts.

@adriaanm adriaanm modified the milestones: 2.12.4, 2.12.5 Sep 27, 2017
@SethTisue SethTisue modified the milestones: 2.12.5, 2.12.6 Feb 22, 2018
@SethTisue
Copy link
Member

@jvican interested in trying to finish this up in time for 2.12.5...? I've changed the milestone to 2.12.6, but only tentatively.

@jvican
Copy link
Member Author

jvican commented Mar 5, 2018

@SethTisue Let's leave it for 2.12.6, since I believe that integrating Harrison's changes will take me a little bit of time and there are things with higher priority on my scala/scala queue 😄.

@SethTisue SethTisue added the WIP label Mar 5, 2018
@SethTisue SethTisue modified the milestones: 2.12.6, 2.12.7 Mar 23, 2018
@adriaanm
Copy link
Contributor

At this point I think we should move this work to 2.13.0-M5. I would also encourage you to integrate the other efforts in this area.

@adriaanm adriaanm closed this Apr 30, 2018
@jvican
Copy link
Member Author

jvican commented Apr 30, 2018

Can i reopen this PR targeting 2.13.0-M5?

@SethTisue
Copy link
Member

sure, you can retarget and reopen

@SethTisue SethTisue removed this from the 2.12.7 milestone Sep 10, 2018
@retronym
Copy link
Member

I'm taking a look at reviving this in 2.13.x...retronym:topic/java-inherited-type-ident

retronym added a commit to retronym/scala that referenced this pull request Jan 14, 2019
retronym added a commit to retronym/scala that referenced this pull request Jan 14, 2019
retronym added a commit to retronym/scala that referenced this pull request Jan 22, 2019
retronym added a commit to retronym/scala that referenced this pull request Jan 22, 2019
retronym added a commit to retronym/scala that referenced this pull request Feb 11, 2019
adriaanm pushed a commit to retronym/scala that referenced this pull request Feb 11, 2019
retronym added a commit that referenced this pull request Feb 11, 2019
 - Inherited type declarations are in scope in Java code
 - For static innner classes, we need to check in the companion
module of each base classes.
 - Incorporate and accomodate test case from #6053
 - Tests to java code referring to module-class owned classes via companion class prefix
retronym added a commit to retronym/scala that referenced this pull request Feb 12, 2019
 - Inherited type declarations are in scope in Java code
 - For static innner classes, we need to check in the companion
module of each base classes.
 - Incorporate and accomodate test case from scala#6053
 - Tests to java code referring to module-class owned classes via companion class prefix

 Backport of scala#7671
retronym added a commit to retronym/scala that referenced this pull request Feb 12, 2019
 - Inherited type declarations are in scope in Java code
 - For static innner classes, we need to check in the companion
module of each base classes.
 - Incorporate and accomodate test case from scala#6053
 - Tests to java code referring to module-class owned classes via companion class prefix

 Backport of scala#7671
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.

7 participants