Skip to content

Change scheme to implement creator applications #10784

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 10 commits into from
Dec 16, 2020

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Dec 14, 2020

Use synthetic apply methods instead of a fallback in Typer to implement
creator applications.

Todo:

@sjrd
Copy link
Member

sjrd commented Dec 14, 2020

Will that affect the bytecode in any way? (new objects, new defs, call sites?)

@sjrd
Copy link
Member

sjrd commented Dec 14, 2020

More importantly: can this change the meaning of existing programs?

  • Through accidental overrides of an existing apply method in a superclass/trait of the companion?
  • Through a change in overload resolution if there are other apply methods in the companion?
  • Through shadowing an apply method available via an implicit conversion/extension method?

All the previous discussions of this feature were based on the fact that it couldn't change the meaning of existing programs. Changing this premise invalidates all those discussions, including at the SIP meetings, where I recall that this was an important concern.

@odersky
Copy link
Contributor Author

odersky commented Dec 14, 2020

More importantly: can this change the meaning of existing programs?

Through accidental overrides of an existing apply method in a superclass/trait of the companion?

No. If an apply method exists (with any type, in either the class itself or in any parent), no creator proxy is generated.

Through a change in overload resolution if there are other apply methods in the companion?

No, since creator proxies methods have lower precedence than anything else (note that the previous condition still allows overloads: An apply could be defined in trait A, and a creator proxy of a different signature in trait B. Then we do get overloaded variants in a class that extends A and B.

Through shadowing an apply method available via an implicit conversion/extension method?

Yes. And there could be other priority inversions as well. For instance here is one I observed in catsEffect2, class `InstancesTests.
It contains a call

AsyncTests[T1, T2]

In the same package a monomorphic class AsyncTests was defined. In an outer import (of lower precedence) an object AsyncTests was defined with a polymorphic apply method. The new scheme created a companion AsyncTests with the class that shadowed the outer companion. The problem could be fixed by importing the object in a more deeply nested scope
so that it takes precedence over the package definition.

We should look into what we can do to avoid breakage like that. Here, we got a type error, but we mighty also resolve to a different method which could change behavior. One possibility would be to try both old and new resolution schemes and issue an error if they are different. We could do that under -source 3.0-migration.

@odersky odersky force-pushed the change-creators branch 2 times, most recently from a62a470 to 4d167e4 Compare December 14, 2020 21:57
@odersky
Copy link
Contributor Author

odersky commented Dec 15, 2020

Here are the breakages observed in the community build:

  • Shadowing problems of the kind described above. Fixed by 6 additional imports or qualifiers in cats-effects-2 (1), cats-mtl (3), scala-parallel collections (2).
  • One issue in zio where some type parameters were not detected due to a cyclic reference that is probably caused by the completion of a constructor proxy (this one needs follow up).
  • A weird issue where scalacheck used a case class named apply and relied on two applys in a row being inserted, which is no longer the case for some reason (I count that as a bug fix).
  • An issue in scas that I believe comes from a build that mixes several compiler versions in an illegal way.

I believe we should address the shadowing problems. We could demand that when resolving a constructor proxy identifier the same identifier does not already exist as a regular identifier in some outer scope. That would have caught each of the shadowing issues. We should also see what we can do to avoid the problem in zio.

Overall I am quite positive now. The new scheme is much safer than the previous one, and it looks compatible enough with Scala 2 to be workable.

@odersky odersky force-pushed the change-creators branch 3 times, most recently from 3f2775f to d697007 Compare December 15, 2020 12:40
@odersky odersky marked this pull request as ready for review December 15, 2020 12:40
@odersky
Copy link
Contributor Author

odersky commented Dec 15, 2020

The code so far can already be reviewed.

@odersky odersky added this to the 3.0.0-M3 milestone Dec 15, 2020
@odersky
Copy link
Contributor Author

odersky commented Dec 15, 2020

I verified that the shadowing check catches and diagnoses the observed errors in the community build.

@odersky
Copy link
Contributor Author

odersky commented Dec 15, 2020

From my point of view, this is ready to merge. We can figure out the problem observed in zio later. If someone still has the time to review this until tomorrow, this would be great. Otherwise we'll merge as is and deal with any possible improvements later.

This was interesting. It looks like we wanted the creator proxy since there was an explicit
import. But we resolved to the outer apply before. Now we see that there's a conflict since
we get the shadowing error.
@odersky
Copy link
Contributor Author

odersky commented Dec 15, 2020

The shapeless error was interesting. It looks like we wanted the creator proxy since there was an explicit
import of its companions class. But we resolved to the outer apply before, so the fallback never kicked in.
Which shows that the fallback scheme can also be misleading.. Now we see that there's a conflict since
we get the shadowing error.

@odersky
Copy link
Contributor Author

odersky commented Dec 15, 2020

Overall I am now quite convinced that the new scheme is better than the previous one, and that the previous one had hidden problems that would have only got worse with widespread adoption. So I am glad we can get it changed in time for 3.0.

@odersky
Copy link
Contributor Author

odersky commented Dec 15, 2020

test performance please

@dottybot
Copy link
Member

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

@dottybot
Copy link
Member

Performance test finished successfully:

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

Benchmarks is based on merging with master (d7ecc34)

@odersky odersky merged commit 3e471ab into scala:master Dec 16, 2020
@odersky odersky deleted the change-creators branch December 16, 2020 06:53
@SethTisue
Copy link
Member

SethTisue commented Dec 16, 2020

before, I could val lock = AnyRef(), now I can't?

I guess it's because AnyRef a is type alias? I see also that class C; type D = C; D() worked before but now doesn't.

is this considered desirable/acceptable?

@rjolly
Copy link
Contributor

rjolly commented Dec 19, 2020

I concur. This is the cause of the failure in ScAS. I have opened an issue: #10862

@odersky
Copy link
Contributor Author

odersky commented Dec 20, 2020

Yes, that's intended. You don't get an apply for a type alias. I think this is what we want. To compare, if I write

type S = scala.collection.immutable.Seq

I still can't do S(1, 2, 3). I'd have to add

val S = scala.collection.immutable.Seq

For the new kind of apply methods, it's the same.

@SethTisue
Copy link
Member

(I guess let's discuss further on #10862)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-notes Should be mentioned in the release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants