Skip to content

Regression in note/mini-refined for inline / match type #20292

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
WojciechMazur opened this issue Apr 29, 2024 · 1 comment
Closed

Regression in note/mini-refined for inline / match type #20292

WojciechMazur opened this issue Apr 29, 2024 · 1 comment
Assignees
Labels
area:inline area:match-types itype:bug regression This worked in a previous version but doesn't anymore

Comments

@WojciechMazur
Copy link
Contributor

Based on OpenCB failure in note/mini-refined - build logs

Compiler version

Last good release: 3.5.0-RC1-bin-20240409-2148c8d-NIGHTLY
First bad release: 3.5.0-RC1-bin-20240411-4f17e25-NIGHTLY
Bisect points to b87ff4b

Minimized code

// test.scala
import scala.language.implicitConversions

object compiletime {
  sealed trait ValidateExprString
  object ValidateExprString:
    class And[A <: ValidateExprString, B <: ValidateExprString] extends ValidateExprString
    class StartsWith[T <: String & Singleton] extends ValidateExprString
    class EndsWith[T <: String & Singleton] extends ValidateExprString

}
import compiletime.*
export compiletime.ValidateExprString.{And as _, *}

type ValidateExpr = compiletime.ValidateExprString
type And[A <: ValidateExpr, B <: ValidateExpr] = (A, B) match
  case (compiletime.ValidateExprString, compiletime.ValidateExprString) =>
    compiletime.ValidateExprString.And[A, B]

infix final class Refined[+T, P <: Refined.ValidateExprFor[T]](val value: T)
object Refined {
  type ValidateExprFor[B] = B match
    case String => ValidateExprString
}

implicit inline def mkValidatedString[V <: String & Singleton, E <: ValidateExprString](
    v: V
): Refined[V, E] =
  inline ValidateString.validate[V, E] match
    case null    => Refined(v)
    case failMsg => scala.compiletime.error(failMsg)

@main def Test = {
  val a: String Refined And[StartsWith["abc"], EndsWith["xyz"]] = "abcdxyz"
}
// test.macro.scala
import quoted.{Expr, Quotes}
import compiletime.*
import scala.compiletime.*

object ValidateString:
  transparent inline def validate[V <: String & Singleton, E <: ValidateExprString]: String | Null =
    inline erasedValue[E] match
      case _: StartsWith[t] =>
        inline startsWith(constValue[V], constValue[t]) match
          case _: true => null
          case _: false => constValue[V]
      case _: EndsWith[t] =>
        inline endsWith(constValue[V], constValue[t]) match
          case _: true => null
          case _: false => constValue[V]
      case _: And[a, b] =>
        // workaround for: https://github.com/lampepfl/dotty/issues/12715
        inline val res = validate[V, a]
        inline res match
          case null => validate[V, b]
          case _    => res

  private transparent inline def startsWith(inline v: String, inline pred: String): Boolean =
    ${ startsWithCode('v, 'pred) }

  private def startsWithCode(v: Expr[String], pred: Expr[String])(using Quotes): Expr[Boolean] =
    val res = v.valueOrAbort.startsWith(pred.valueOrAbort)
    Expr(res)

  private transparent inline def endsWith(inline v: String, inline pred: String): Boolean =
    ${ endsWithCode('v, 'pred) }

  private def endsWithCode(v: Expr[String], pred: Expr[String])(using Quotes): Expr[Boolean] =
    val res = v.valueOrAbort.endsWith(pred.valueOrAbort)
    Expr(res)

Output

-- Error: /Users/wmazur/projects/dotty/bisect/test.scala:33:66 -----------------
33 |  val a: String Refined And[StartsWith["abc"], EndsWith["xyz"]] = "abcdxyz"
   |                                                                  ^^^^^^^^^
   |                          `inline val` with `null` is not supported.
   |
   |                          To inline a `null` consider using `inline def`
   |----------------------------------------------------------------------------
   |Inline stack trace
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from test.scala:19
19 |infix final class Refined[+T, P <: Refined.ValidateExprFor[T]](val value: T)
   |             ^^^^^^^^^^^^^^

Expectation

It should compile in the same way as it did in 3.3 and 3.4 inline val with null rule can be tracked to even earlier versions of the compiler.

@jchyb
Copy link
Contributor

jchyb commented Jun 24, 2024

Fixed by PRing a fix in the original repository, since the behavior here is expected. This issue appeared after #20125, which made it so that information is not lost from an inline match scrutinee - before the whole block would get incorrectly removed, so in this project specifically, the inline val would no longer exist and the check would not be run on it. The check for null also has to stay, as nulls currently cannot participate in constant folding, which is why the check was added to begin with.

@jchyb jchyb closed this as completed Jun 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:inline area:match-types itype:bug regression This worked in a previous version but doesn't anymore
Projects
None yet
Development

No branches or pull requests

3 participants