8000 zio-prelude compilation fails with ScalaJS 1.18.x · Issue #5127 · scala-js/scala-js · GitHub
[go: up one dir, main page]

Skip to content

zio-prelude compilation fails with ScalaJS 1.18.x #5127

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

Closed
guizmaii opened this issue Feb 6, 2025 · 4 comments
Closed

zio-prelude compilation fails with ScalaJS 1.18.x #5127

guizmaii opened this issue Feb 6, 2025 · 4 comments
Labels
question This is a question on using Scala.js. Closed because it should go to Stack Overflow or gitter.

Comments

@guizmaii
Copy link
guizmaii commented Feb 6, 2025

When compiling zio-prelude with ScalaJS 1.18.x, the compilation fails with multiple errors:

error] 
[error]   Cannot resolve delambdafy target method $anonfun$assertion$1 at source-/home/runner/work/zio-prelude/zio-prelude/core-tests/shared/src/test/scala-2.12-2.13/zio/prelude/NewtypeSpecTypes212.scala,line-7,offset=161
[error]      while compiling: /home/runner/work/zio-prelude/zio-prelude/core-tests/shared/src/test/scala-2.12-2.13/zio/prelude/NewtypeSpecTypes212.scala
[error]         during phase: jscode
[error]      library version: version 2.13.16
[error]     compiler version: version 2.13.16
[error] 
[error]   last tree to typer: TypeTree(class List)
[error]        tree position: line 361 of /home/runner/work/zio-prelude/zio-prelude/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala
[error]             tree tpe: List
[error]               symbol: (sealed abstract) class List in package immutable
[error]    symbol definition: sealed abstract class List extends AbstractSeq with LinearSeq with LinearSeqOps with StrictOptimizedLinearSeqOps with StrictOptimizedSeqOps with IterableFactoryDefaults with DefaultSerializable (a ClassSymbol)
[error]       symbol package: scala.collection.immutable
[error]        symbol owners: class List
[error]            call site: <$anon: Function1> in package fx in package fx
[error] 
[error] == Source file context for tree position ==
[error] 
[error]    358           val actual   = values.foldMapM(errorOnOddNumber)
[error]    359 
[error]    360           assert(actual)(equalTo(expected)) &&
[error]    3[61](https://github.com/zio/zio-prelude/actions/runs/13171977585/job/36763853813#step:6:62)           assert(calledOn)(equalTo(List(3, 2)))
[error]    3[62](https://github.com/zio/zio-prelude/actions/runs/13171977585/job/36763853813#step:6:63)         }
[error]    3[63](https://github.com/zio/zio-prelude/actions/runs/13171977585/job/36763853813#step:6:64)       )
[error]    364     )
[error] Error while emitting NewtypeSpecTypes212.scala
[error] 
[error]   Cannot resolve delambdafy target method $anonfun$assertion$1 at source-/home/runner/work/zio-prelude/zio-prelude/core-tests/shared/src/test/scala-2.12-2.13/zio/prelude/NewtypeSpecTypes212.scala,line-7,offset=161
[error]      while compiling: /home/runner/work/zio-prelude/zio-prelude/core-tests/shared/src/test/scala-2.12-2.13/zio/prelude/NewtypeSpecTypes212.scala
[error]         during phase: jscode
[error]      library version: version 2.13.16
[error]     compiler version: version 2.13.16

The PR updating SclaJS can be found here: zio/zio-prelude#1462
The CI build failing can be found here: https://github.com/zio/zio-prelude/actions/runs/13171977585/job/36763853813

NewtypeSpecTypes212.scala code is:

package zio.prelude

// scalafix:off
object NewtypeSpecTypes212 {

  object Palindrome extends Newtype[String] {
    override def assertion = assertCustom { str =>
      if (str.reverse == str) Right(()) else Left(AssertionError.Failure("isPalindrome"))
    }
  }

  Palindrome("racecar")
}
// scalafix:on

The code in ForEachSpec.scala failing to compile seems to be the following one:

        test("shortcircuits sideffects according to effect") {
          var calledOn: List[Int] = Nil

          def errorOnOddNumber(i: Int): Either[Int, Sum[Int]] = {
            calledOn = i :: calledOn

            if (i % 2 == 0) Right(Sum(i))
            else Left(i)
          }

          val values   = List(2, 3, 4)
          val expected = Left(3)
          val actual   = values.foldMapM(errorOnOddNumber)

          assert(actual)(equalTo(expected)) &&
          assert(calledOn)(equalTo(List(3, 2)))
        }

To reproduce locally, clone the repo and launch in an sbt console:

+rootJS/Test/compile
@sjrd
Copy link
Member
sjrd commented Feb 6, 2025

There's a simpler example in examples/shared/src/main/CustomFunctionExample.scala. For that file, we see the following code after pickler then mixin (having removed some annotations):

[[syntax trees at end of                   pickler]] // CustomFunctionExample.scala
package examples {
  import zio.prelude._;
  object CustomFunctionExample extends scala.AnyRef {
    def <init>(): examples.CustomFunctionExample.type = {
      CustomFunctionExample.super.<init>();
      ()
    };
    object Palindrome extends zio.prelude.Newtype[String] {
      def <init>(): examples.CustomFunctionExample.Palindrome.type = {
        Palindrome.super.<init>();
        ()
      };
      override def assertion: zio.prelude.QuotedAssertion[String]{def magic: Int} = {
        final class $anon extends AnyRef with zio.prelude.QuotedAssertion[String] {
          def <init>(): <$anon: zio.prelude.QuotedAssertion[String]> = {
            $anon.super.<init>();
            ()
          };
          def magic: Int = 42;
          def run(value: String): scala.util.Either[zio.prelude.AssertionError,Unit] =
            ((str: String) =>
              if (scala.Predef.augmentString(str).reverse.==(str))
                scala.`package`.Right.apply[Nothing, Unit](())
              else
                scala.`package`.Left.apply[zio.prelude.AssertionError.Failure, Nothing](zio.prelude.AssertionError.Failure.apply("isPalindrome"))
            ).apply(value)
        };
        new $anon()
      }
    };
    zio.prelude.Newtype.unsafeWrap[examples.CustomFunctionExample.Palindrome.type](CustomFunctionExample.this.Palindrome)("racecar")
  }
}
[[syntax trees at end of                     mixin]] // CustomFunctionExample.scala
package examples {
  object CustomFunctionExample extends Object {
    def <init>(): examples.CustomFunctionExample.type = {
      CustomFunctionExample.super.<init>();
      zio.prelude.Newtype.unsafeWrap(examples.CustomFunctionExample$Palindrome, "racecar");
      ()
    }
  };
  object CustomFunctionExample$Palindrome extends zio.prelude.Newtype {
    override def assertion(): zio.prelude.QuotedAssertion = new <$anon: zio.prelude.QuotedAssertion>();
    final <artifact> private[this] def $anonfun$assertion$1(str: String): scala.util.Either =
      if (scala.collection.StringOps.reverse$extension(scala.Predef.augmentString(str)).==(str))
        new scala.util.Right(scala.runtime.BoxedUnit.UNIT)
      else
        new scala.util.Left(new zio.prelude.AssertionError$Failure("isPalindrome"));
    def <init>(): examples.CustomFunctionExample$Palindrome.type = {
      CustomFunctionExample$Palindrome.super.<init>();
      ()
    }
  };
  final class anon$1 extends Object with zio.prelude.QuotedAssertion {
    def run(value: String): scala.util.Either =
      {
        ((str: String) => CustomFunctionExample$Palindrome.this.$anonfun$assertion$1(str))
      }.apply(value).$asInstanceOf[scala.util.Either]();
    <bridge> <artifact> def run(value: Object): scala.util.Either = anon$1.this.run(value.$asInstanceOf[String]());
    def <init>(): <$anon: zio.prelude.QuotedAssertion> = {
      anon$1.super.<init>();
      ()
    }
  }
}

In the last snippet we see something funny. We have a lambda (str: String) => ... whose body calls a method from another class. That's not supposed to happen. I mean, what even is CustomFunctionExample$Palindrome.this in that constext!?

It breaks the new codegen we introduced in #5081 . But I also struggle to understand how the old codegen could deal with that? 🤔

I suspect that a macro or even a compiler plugin is responsible for generating that unexpected code pattern. @guizmaii Do you have any insight into that?

@sjrd
Copy link
Member
sjrd commented Feb 6, 2025

Indeed, it looks like that code is the result of the following macro:
https://github.com/zio/zio-prelude/blob/586acba755503a258b088107aa248b5ebf5ea5c3/macros/shared/src/main/scala-2/zio/prelude/Macros.scala#L197-L209

  def assertCustom_impl[A: c.WeakTypeTag](f: c.Tree): c.Tree = {
    val (_, _, codeString) = text(f)
    q"""
new _root_.zio.prelude.QuotedAssertion[${c.weakTypeOf[A]}] {
  @_root_.zio.prelude.assertionLambdaQuote($f)
  @_root_.zio.prelude.assertionString($codeString)
  def magic = 42

  def run(value: ${c.weakTypeOf[A]}): _root_.scala.util.Either[_root_.zio.prelude.AssertionError, _root_.scala.Unit] =
    $f(value)
}
       """
  }

That macro seems to splice in $f inside a method of the new anonymous class new ..QuotedAssertion { ... }. Is that supposed to be allowed in Scala 2 macros? Aren't you supposed to adapt the owner chain of things inside f? If yes, then this macro fails to do that, and that means the rest of the compilation thinks that the anon function in f still belongs to the enclosing Palindrome. The transformation phases would then start to do weird things, which could lead to the problematic code pattern we're seeing above.

@ghik
Copy link
ghik commented Feb 6, 2025

Passing f through c.untypecheck (in both splices) seems to fix the compilation error - not sure if doesn't break something else.

guizmaii added a commit to zio/zio-prelude that referenced this issue Feb 7, 2025
* Update sbt-scalajs, scalajs-compiler, ... to 1.18.2

* Give a try to the solution provided by @ghik here: scala-js/scala-js#5127 (comment)

---------

Co-authored-by: zio-scala-steward[bot] <145262613+zio-scala-steward[bot]@users.noreply.github.com>
Co-authored-by: Jules Ivanic <jules.ivanic@gmail.com>
@guizmaii
Copy link
Author
guizmaii commented Feb 7, 2025

Thanks for your help mates!

I made the fix @ghik was suggested and the tests pass 🙂

@guizmaii guizmaii closed this as completed Feb 7, 2025
@sjrd sjrd added the question This is a question on using Scala.js. Closed because it should go to Stack Overflow or gitter. label Feb 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question This is a question on using Scala.js. Closed because it should go to Stack Overflow or gitter.
Projects
None yet
Development

No branches or pull requests

3 participants
0