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

Unable to find extant inner trait IR during fastopt #4148

Closed
djspiewak opened this issue Aug 15, 2020 · 11 comments
Closed

Unable to find extant inner trait IR during fastopt #4148

djspiewak opened this issue Aug 15, 2020 · 11 comments
Assignees
Labels
bug
Milestone

Comments

@djspiewak
Copy link

@djspiewak djspiewak commented Aug 15, 2020

Please refer to this tag: https://github.com/djspiewak/cats-effect/tree/bug/scalajs-concurrentops

You can reproduce the issue with sbt coreJS/test. lawsJS/test also reproduces it. ScalaJS 1.1.1, Scala 2.13.3/2.12.11 (reproduces on both). Compilation finishes without a problem, but fastOpt does not:

[error] Referring to non-existent class cats.effect.Concurrent$Ops
[error]   called from private cats.effect.Concurrent$.$anonfun$continual$1(java.lang.Object,cats.effect.Concurrent,scala.Function1,cats.effect.concurrent.Deferred)java.lang.Object
[error]   called from cats.effect.Concurrent$.continual(java.lang.Object,scala.Function1,cats.effect.Concurrent)java.lang.Object
[error]   called from cats.effect.Concurrent.continual(java.lang.Object,scala.Function1)java.lang.Object
[error]   called from private cats.effect.internals.MVarConcurrent.$anonfun$modify$3(scala.Function1,cats.effect.concurrent.Ref,java.lang.Object)java.lang.Object
[error]   called from private cats.effect.internals.MVarConcurrent.$anonfun$modify$1(scala.Function1,cats.effect.concurrent.Ref)java.lang.Object
[error]   called from cats.effect.internals.MVarConcurrent.modify(scala.Function1)java.lang.Object
[error]   called from private cats.effect.concurrent.BaseMVarTests.$anonfun$new$212(cats.effect.concurrent.MVar2,int)cats.effect.IO
[error]   called from private cats.effect.concurrent.BaseMVarTests.$anonfun$new$211(cats.effect.concurrent.MVar2,java.lang.Void)cats.effect.IO
[error]   called from private cats.effect.concurrent.BaseMVarTests.$anonfun$new$210(cats.effect.concurrent.MVar2)cats.effect.IO
[error]   called from private cats.effect.concurrent.BaseMVarTests.$anonfun$new$209()scala.concurrent.Future
[error]   called from constructor cats.effect.concurrent.BaseMVarTests.<init>()void
[error]   called from constructor cats.effect.concurrent.MVarAsyncTests.<init>()void
[error]   called from static constructor cats.effect.concurrent.MVarAsyncTests.<clinit>()void
[error]   called from core module analyzer
[error] involving instantiated classes:
[error]   cats.effect.Concurrent$
[error]   cats.effect.internals.MVarConcurrent

There's a bunch of that, but this is the topmost one. The perplexing thing is that the Concurrent$Ops.sjsir file exists, and coreJVM/test works just fine. The error survives through clean and even rm -rf *; git reset --hard HEAD.

Note that this source code used to use Simulacrum, but I removed that on this tag. You can see the pre-removal state in HEAD^ on the tag. The source code itself is identical to the series/2.x branch on the same repository, which builds and tests just fine with ScalaJS 1.1.1.

I'm going to keep poking around with the plugins to see if I can figure out where the error was introduced, but this seems like an SJS bug.

@sjrd
Copy link
Member

@sjrd sjrd commented Aug 15, 2020

I found the issue. At
djspiewak/cats-effect@af7098a#diff-34c1cf2624faf6cdff411858a06429edR518
you introduce trait Ops, and at
djspiewak/cats-effect@af7098a#diff-34c1cf2624faf6cdff411858a06429edR536
you introduce object ops. Those two differ only by case.

If they were at the top-level, scalac would report a warning that this is going to break on Windows, because it generates a class ops with static forwarders to the methods of object ops. Scala.js does that as well, but it also does it for non-top-level objects (as long as they're static, i.e., inside objects), whereas Scala/JVM doesn't do it.

That means that when emitting this file, the generated class Concurrent$ops overrides the interface Concurrent$Ops (at least on Windows). We should definitely emit a warning/error when that happens.

Try renaming either trait Ops or object ops so that they have different names even under a case-insensitive comparison.

@djspiewak
Copy link
Author

@djspiewak djspiewak commented Aug 15, 2020

Wow! That's fascinating. What I don't understand is why series/2.x doesn't hit this, since Simulacrum is generating this same stuff. I'm going to need to play with this a bit to see if I can figure out how to fix this without breaking binary compatibility.

@povder
Copy link

@povder povder commented Aug 15, 2020

@sjrd
Copy link
Member

@sjrd sjrd commented Aug 15, 2020

Perhaps simulacrum generates the objects first and the trait second. In that order, things "work" because the real trait overrides the class with static forwarders, not the other way around.

That can also serve as a workaround, by the way.

@djspiewak
Copy link
Author

@djspiewak djspiewak commented Aug 16, 2020

That is only moderately horrifying.

@sjrd
Copy link
Member

@sjrd sjrd commented Aug 16, 2020

I believe we could fix this in Scala.js without breaking actual functionality, as follows. When emitting the static forwarders of a module class:

  • if it doesn't have a companion class/trait, and
  • if there exists another generated class whose encoded name differs only in case with the name of the class that would hold the static forwarders,

then do not emit the synthetic class with the static forwarders.

The first condition makes sense because, if it has a companion class, then the code is already halfway broken on Scala/JVM and scalac already emits a warning that it's going to break on case-insensitive file systems.

Since currently, emitting these static forwarders produces completely broken products, fixing the issue as above won't introduce regressions for existing correct code.

@gzm0 WDYT? Does that make sense?

@gzm0
Copy link
Contributor

@gzm0 gzm0 commented Aug 16, 2020

Hum, wouldn't this break things on case insensitive systems? (i.e. a synthesized companion just "disappears" because there happens to be a thing with a name that only differs in case)? (IIUC Scala is a case sensitive language).

@gzm0
Copy link
Contributor

@gzm0 gzm0 commented Aug 16, 2020

(that was meant to be case sensitive systems)

@sjrd
Copy link
Member

@sjrd sjrd commented Aug 16, 2020

It could break something, in theory, but that would have to something that actually relies on the presence of static forwarders. And that never happens for Scala code. It's only necessary for libraries implementing JDK classes, and for JDK classes we know that there are no pair of classes that differ only in case (otherwise the JDK would be broken on Windows).

@gzm0
Copy link
Contributor

@gzm0 gzm0 commented Aug 16, 2020

What about module initializers? They rely on static forwarders, no? (The way I got to this: If only the JDK needs static forwarders, we should make them compile time opt-in. Ah no, we can't do that, module inits...)

@sjrd
Copy link
Member

@sjrd sjrd commented Aug 16, 2020

Realistically, module initializers are only used for main methods. And main methods would not work on the JVM in nested objects in the first place, since Scala/JVM never emits static forwarders for nested objects. From there, the likelihood that someone has a nested main method in an object whose name differs only in case with another class/trait nested at the same level in its enclosing object is ... close to zero. And it would already have been non-working on Windows.

It could happen. In that case the solution would be to rename one or the other. Since it's at the level of a main method, this is unlikely to be a public API that would be bound by binary compatibility constraints.

sjrd added a commit to sjrd/scala-js that referenced this issue Aug 16, 2020
…er class.

This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.
@sjrd sjrd self-assigned this Aug 16, 2020
@sjrd sjrd added the bug label Aug 16, 2020
@sjrd sjrd added this to the v1.2.0 milestone Aug 16, 2020
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 16, 2020
…er class.

This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 17, 2020
…er class.

This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 17, 2020
…er class.

This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 18, 2020
…jects by default.

Scala/JVM does not emit static forwarders for non-top-level
objects. The reason we do it in Scala.js is for implementations of
JDK classes that contain static methods in inner static classes
(scala-js#3950). However, this causes issues with existing Scala projects
that have non-top-level `object`s and `class`es with names
differing only in case.

In this commit, we now only emit static forwarders for top-level
objects by default, and add an opt-in option to emit them for
non-top-level objects, which should be used by implementations of
JDK classes.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 18, 2020
This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.

When doing so, we emit a warning. On Scala 2.13.2+, the warning can
be silenced with `@nowarn("other")` on the object, or
`-Wconf:cat=other:s` in the scalac options.

For top-level objects, the warning is similar to a warning already
emitted by the JVM back-end. For non-top-level objects, it only
happens in Scala.js, and only when they are emitted due to the
opt-in option.

This commit complements its parent in fixing scala-js#4148 for good, as now
that issue won't present itself even when we opt in for static
forwards of non-top-level objects. We therefore add a test for
requires the opt-in for other reasons.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 18, 2020
This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.

When doing so, we emit a warning. On Scala 2.13.2+, the warning can
be silenced with `@nowarn("other")` on the object, or
`-Wconf:cat=other:s` in the scalac options.

For top-level objects, the warning is similar to a warning already
emitted by the JVM back-end. For non-top-level objects, it only
happens in Scala.js, and only when they are emitted due to the
opt-in option.

This commit complements its parent in fixing scala-js#4148 for good, as now
that issue won't present itself even when we opt in for static
forwards of non-top-level objects. We therefore add a test for
requires the opt-in for other reasons.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 19, 2020
…jects by default.

Scala/JVM does not emit static forwarders for non-top-level
objects. The reason we do it in Scala.js is for implementations of
JDK classes that contain static methods in inner static classes
(scala-js#3950). However, this causes issues with existing Scala projects
that have non-top-level `object`s and `class`es with names
differing only in case.

In this commit, we now only emit static forwarders for top-level
objects by default, and add an opt-in option to emit them for
non-top-level objects, which should be used by implementations of
JDK classes.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 19, 2020
This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.

When doing so, we emit a warning. On Scala 2.13.2+, the warning can
be silenced with `@nowarn("other")` on the object, or
`-Wconf:cat=other:s` in the scalac options.

For top-level objects, the warning is similar to a warning already
emitted by the JVM back-end. For non-top-level objects, it only
happens in Scala.js, and only when they are emitted due to the
opt-in option.

This commit complements its parent in fixing scala-js#4148 for good, as now
that issue won't present itself even when we opt in for static
forwards of non-top-level objects. We therefore add a test for
whole requires the opt-in for other reasons.
sjrd added a commit to sjrd/scala-js that referenced this issue Aug 19, 2020
This is only relevant for case insensitive file systems. On such
file systems, a static nested object could generate a static
forwarder class whose name differed from a regular class' name only
in case, therefore erasing the latter on case insensitive file
systems.

We now avoid emitting static forwarder classes for objects if they
would clash with a regular class on a case insensitive file system.

When doing so, we emit a warning. On Scala 2.13.2+, the warning can
be silenced with `@nowarn("other")` on the object, or
`-Wconf:cat=other:s` in the scalac options.

For top-level objects, the warning is similar to a warning already
emitted by the JVM back-end. For non-top-level objects, it only
happens in Scala.js, and only when they are emitted due to the
opt-in option.

This commit complements its parent in fixing scala-js#4148 for good, as now
that issue won't present itself even when we opt in for static
forwards of non-top-level objects. We therefore add a test for
that issue, which we could not do before because the test suite as
a whole requires the opt-in for other reasons.
@sjrd sjrd closed this in #4152 Aug 19, 2020
sjrd added a commit that referenced this issue Aug 19, 2020
…evel-objects-by-default

Fix #4148: Do not emit static forwarders for non-top-level objects by default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

4 participants
You can’t perform that action at this time.