Git Product home page Git Product logo

Comments (12)

saeltz avatar saeltz commented on August 17, 2024 2

would you be interested in trying to fix it?

I first want to get #489 over the finish line.

from chimney.

MateuszKubuszok avatar MateuszKubuszok commented on August 17, 2024 1

I don't really know the value of vector and can't find it. I assume it's a Vector[io.moia.food.food.Meal.Color] where the To type is gotten by index. Is there a way to get that? I could then try to use the derived code and debug further.

vector is not printed but it's Vector[Any] where all runtime data is stored (basically all withFieldConst/withFieldComputed/... values). If e.g you took

  given Transformer[Color, food.Meal.Color] = Transformer
    .define[Color, food.Meal.Color]
    .withCoproductInstance[Color.Yellow.type](_ => food.Meal.Color.COLOR_YELLOW)
    .withCoproductInstance[Color.Red.type](_ => food.Meal.Color.COLOR_RED)
    .withCoproductInstance[Color.orange.type](_ => food.Meal.Color.COLOR_ORANGE)
    .withCoproductInstance[Color.pink.type](_ => food.Meal.Color.COLOR_PINK)
    .withCoproductInstance[Color.Blue.type](_ => food.Meal.Color.COLOR_BLUE)
    .buildTransformer

and did this:

  given Transformer[Color, food.Meal.Color] =
    val foo = Transformer
      .define[Color, food.Meal.Color]
      .withCoproductInstance[Color.Yellow.type](_ => food.Meal.Color.COLOR_YELLOW)
      .withCoproductInstance[Color.Red.type](_ => food.Meal.Color.COLOR_RED)
      .withCoproductInstance[Color.orange.type](_ => food.Meal.Color.COLOR_ORANGE)
      .withCoproductInstance[Color.pink.type](_ => food.Meal.Color.COLOR_PINK)
      .withCoproductInstance[Color.Blue.type](_ => food.Meal.Color.COLOR_BLUE)
    println(foo.runtimeData)
    foo.buildTransformer

you'd see its content.

from chimney.

jchyb avatar jchyb commented on August 17, 2024 1

I looked at this yesterday and it seems that the issue is not with the pattern match case catching incorrect values but with incorrect runtimeData being used in the rhs there. E.g.:

//> using dep "io.scalaland::chimney:0.8.5"

import io.scalaland.chimney.dsl.*
import io.scalaland.chimney.Transformer

sealed trait Color
object Color {
  case object orange extends Color
  case object pink extends Color
}

sealed trait Target
object Target {
  case class Impl(value: String) extends Target
}

@main def main() = 
  val writer: Transformer[Color, Target] = Transformer
    .define[Color, Target]
    .withCoproductInstance[Color.orange.type](_ => Target.Impl("orange"))
    .withCoproductInstance[Color.pink.type](_ => Target.Impl("pink"))
    .enableMacrosLogging
    .buildTransformer

  writer.transform(Color.pink)

when running this with -Xprint:inlining, we get (I trimmed some of the types for readability):

val writer: io.scalaland.chimney.Transformer[Color, Target] =
          {
            val TransformerDefinition_this:  io.scalaland.chimney.dsl.TransformerDefinition[...]
             = io.scalaland.chimney.Transformer.define[Color, Target]
            val TransformerDefinition_this: 
                io.scalaland.chimney.dsl.TransformerDefinition[...]
             =
              io.scalaland.chimney.internal.runtime.WithRuntimeDataStore.update[ // idx: 0, orange
               ...
              ](TransformerDefinition_this,
                {
                  def $anonfun(_$1: Color.orange.type): Target =
                    Target.Impl.apply("orange")
                  closure($anonfun)
                }
              ).asInstanceOf[...]
            {
              val TransformerDefinition_this:
                  io.scalaland.chimney.dsl.TransformerDefinition[...]
               =
                io.scalaland.chimney.internal.runtime.WithRuntimeDataStore.
                  update[...](TransformerDefinition_this, // idx: 1, pink
                  {
                    def $anonfun(_$2: Color.pink.type): Target =
                      Target.Impl.apply("pink")
                    closure($anonfun)
                  }
                ).asInstanceOf[...].enableMacrosLogging
              {
                val vector$macro$1: Vector[Any] =
                  TransformerDefinition_this.runtimeData
                {
                  {
                    (vector$macro$1:Vector[Any] @unchecked match 
                      {
                        case _ => ()
                      }
                    )
                    ()
                  }
                  {
                    final class $anon() extends Object(), io.scalaland.chimney.
                      Transformer[Color, Target] {
                      def transform(src: Color): Target =
                        {
                          val color$macro$1: Color = src
                          color$macro$1:Color @unchecked match 
                            {
                              case orange$macro$2 @ Color.orange =>
                                val orange$macro$1: Color.orange.type =
                                  orange$macro$2
                                {
                                  orange$macro$1:Color.orange.type @unchecked
                                     match 
                                    {
                                      case _ => ()
                                    }
                                  ()
                                }
                                vector$macro$1.apply(1).asInstanceOf[ // idx 1: pink for type orange
                                  Color => Target].apply(
                                  orange$macro$1.asInstanceOf[Color])
                              case pink$macro$2 @ Color.pink =>
                                val pink$macro$1: Color.pink.type = pink$macro$2
                                {
                                  pink$macro$1:Color.pink.type @unchecked match 
                                    {
                                      case _ => ()
                                    }
                                  ()
                                }
                                vector$macro$1.apply(0).asInstanceOf[ // idx: 0, orange, for type pink
                                  Color => Target].apply(
                                  pink$macro$1.asInstanceOf[Color])
                            }
                        }
                    }
                    new $anon():io.scalaland.chimney.Transformer[Color, Target]
                  }
                }
              }:io.scalaland.chimney.Transformer[Color, Target]
            }
          }

This can also be replicated in tests, however changing the order of withCoproductInstance will "fix" the issue, so I imagine that is why it was not discovered there. I have not managed to fix this yet, unfortunately

from chimney.

MateuszKubuszok avatar MateuszKubuszok commented on August 17, 2024 1

@jchyb I believe that it a bug in pattern matching on Scala 3 after all. (Or in detecting case objects).

I compared:

  • type level representations of config
  • coproductOverrides

in

They are correct - runtime values are prepended so that what is in Vector[Any] corresponds with the position with type-level-list of overrides (rename is an exception but in everything else prepends both on type-level and on value-level).

  val writer: Transformer[Color, Target] = Transformer
    .define[Color, Target]
    .withCoproductInstance[Color.orange.type](_ => Target.Impl("orange")) // last but one override -> idx = 1
    .withCoproductInstance[Color.pink.type](_ => Target.Impl("pink")) // last override -> idx = 0
    .enableMacrosLogging
    .buildTransformer

What changes is the order of case clauses in a pattern matching - at first Chimney generates them from CoproductInstance overrides - in the order it found them in a config, so it's deterministic - and then fill the remaining subtypes by matching names.

You can see that failing example has orange as the first case and working one has pink as the first case. Pass orange and the outcome reverses.

(I had a much longer comment but I accidentally refreshed and it all got removed :| )

I was certain that I test for these, but apparently the test was not detailed enough. :/

EDIT:

I tried to dig around, I refactored the code a bit, to eliminate leaky abstraction and imprecise methods names to make the code easier to read, but was not successful. Unfortunately, Ident.unapply returns String instead of a symbol, so I have to guess what exactly goes into it when compiler constructs an ADT that is not throwing :/

EDIT2:

I modified the first test of SealedHierarchySpec

    (colors1.Red: colors1.Color).transformInto[colors2.Color] ==> colors2.Red
    (colors1.Green: colors1.Color).transformInto[colors2.Color] ==> colors2.Green
    (colors1.Blue: colors1.Color).transformInto[colors2.Color] ==> colors2.Blue

    (colors1.Red: colors1.Color)
      .into[colors2.Color]
      .withCoproductInstance[colors1.Red.type](_ => colors2.Red)
      .withCoproductInstance[colors1.Blue.type](_ => colors2.Blue)
      .transform ==> colors2.Red

    (colors1.Blue: colors1.Color)
      .into[colors2.Color]
      .withCoproductInstance[colors1.Red.type](_ => colors2.Red)
      .withCoproductInstance[colors1.Blue.type](_ => colors2.Blue)
      .transform ==> colors2.Blue

    (colors1.Green: colors1.Color)
      .into[colors2.Color]
      .withCoproductInstance[colors1.Red.type](_ => colors2.Red)
      .withCoproductInstance[colors1.Blue.type](_ => colors2.Blue)
      .transform ==> colors2.Green

it... passed. So I tried one more thing. I changed all names to PascalCase:

import io.scalaland.chimney.dsl.*
import io.scalaland.chimney.Transformer

sealed trait Color
object Color {
  case object Orange extends Color
  case object Pink extends Color
}

sealed trait Target
object Target {
  case class Impl(value: String) extends Target
}

val testDupa = Transformer
  .define[Color, Target]
  .withCoproductInstance[Color.Orange.type](_ => Target.Impl("orange")) // 1
  .withCoproductInstance[Color.Pink.type](_ => Target.Impl("pink")) // 0
  .enableMacrosLogging

val testDupa2 = Transformer
  .define[Color, Target]
  .withCoproductInstance[Color.Pink.type](_ => Target.Impl("pink")) // 1
  .withCoproductInstance[Color.Orange.type](_ => Target.Impl("orange")) // 0
  .enableMacrosLogging

val writer: Transformer[Color, Target] = testDupa // orange -> 1, pink -> 0
  .buildTransformer

scala.util.Try(writer.transform(Color.Pink))

val writer2: Transformer[Color, Target] = testDupa2 // pink -> 1, orange -> 0
  .buildTransformer

scala.util.Try(writer2.transform(Color.Pink))
scala.util.Try(writer2.transform(Color.Orange))

It works without any issues.

I tried to skip withCoproductInstance but match it against virtually the same sealed hierarchy - also with lowercase names.

It fails in exactly the same way.

This explains why no other tests detected it. This only fails for a combination of:

  • Scala 3
  • sealed trait
  • with case objects with lowercased names

😱

from chimney.

MateuszKubuszok avatar MateuszKubuszok commented on August 17, 2024 1

Reported as scala/scala3#20350

from chimney.

MateuszKubuszok avatar MateuszKubuszok commented on August 17, 2024

Thank you for the report!

As I wrote in #480, I don't have much time on my hand so I am not sure when I'll be able to look at the reproduction.

If you wanted to investivate further in the meantime

  • it could be interesting to take a look at the generated code, to see if handwritten version would have the same issue

  • it's very curious , if clean and compile can change the outcome, as it suggest it might be something about Zink and/or classloader. I started observing many issues ever since I updated Scala 3 to 3.3.3 - on 3.3.1 code was compiling fine, but after update I get a lot of broken classpath exceptions, and I have to compile sometimes 3 times or more before it works. I was thinking it might be an artifact of some weird changes to build.sbt I attempted a few days ago, but maybe it's related to the Scala 3 update. I see you have 3.3.3 as well. What would happen if you downgrade to 3.3.1?

from chimney.

saeltz avatar saeltz commented on August 17, 2024

The erroneous derived transformer looks like:

[info]    || {
[info]    ||   final class $anon() extends io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color] {
[info]    ||     def transform(src: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color): io.moia.food.food.Meal.Color = {
[info]    ||       val color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color = src
[info]    ||       (color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color @scala.unchecked) match {
[info]    ||         case pink @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink =>
[info]    ||           val pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink.type = pink
[info]    ||           {
[info]    ||             (pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(1).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(pink.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case red @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red =>
[info]    ||           val red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red.type = red
[info]    ||           {
[info]    ||             (red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(3).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(red.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case yellow @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow =>
[info]    ||           val yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow.type = yellow
[info]    ||           {
[info]    ||             (yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(4).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(yellow.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case blue @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue =>
[info]    ||           val blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue.type = blue
[info]    ||           {
[info]    ||             (blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(0).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(blue.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case orange @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange =>
[info]    ||           val orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange.type = orange
[info]    ||           {
[info]    ||             (orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(2).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(orange.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||       }
[info]    ||     }
[info]    ||   }
[info]    || 
[info]    ||   (new $anon(): io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color])
[info]    || }

The enum one:

[info]    || {
[info]    ||   final class $anon() extends io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color] {
[info]    ||     def transform(src: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color): io.moia.food.food.Meal.Color = {
[info]    ||       val color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color = src
[info]    ||       (color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color @scala.unchecked) match {
[info]    ||         case blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue.type =>
[info]    ||           val blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue.type = blue
[info]    ||           {
[info]    ||             (blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(0).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(blue.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow.type =>
[info]    ||           val yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow.type = yellow
[info]    ||           {
[info]    ||             (yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(4).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(yellow.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red.type =>
[info]    ||           val red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red.type = red
[info]    ||           {
[info]    ||             (red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(3).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(red.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink.type =>
[info]    ||           val pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink.type = pink
[info]    ||           {
[info]    ||             (pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(1).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(pink.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange.type =>
[info]    ||           val orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange.type = orange
[info]    ||           {
[info]    ||             (orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(2).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(orange.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||       }
[info]    ||     }
[info]    ||   }
[info]    || 
[info]    ||   (new $anon(): io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color])
[info]    || }

Ignoring all the boilerplate, the only difference is the colon : instead of an @ in each case of the pattern match.

I don't really know the value of vector and can't find it. I assume it's a Vector[io.moia.food.food.Meal.Color] where the To type is gotten by index. Is there a way to get that? I could then try to use the derived code and debug further.

from chimney.

saeltz avatar saeltz commented on August 17, 2024

Using Scala 3.3.1 the problem still exists. Also on Scala 3.4. (Although I found another issue with ScalaPB: scalapb/ScalaPB#1662)

from chimney.

saeltz avatar saeltz commented on August 17, 2024

I minimised the example even further in https://github.com/moia-oss/teleproto/blob/chimney2/src/test/scala/io/moia/protos/teleproto/ProtocolBuffersRoundTripTest.scala.

I found another difference: It's not only the : but also the .type in the same line.

The ClassCastException is thrown by the @unchecked annotation as documented here:
https://www.scala-lang.org/api/current/scala/unchecked.html. I don't really understand why it's not thrown in the enum variant.

from chimney.

MateuszKubuszok avatar MateuszKubuszok commented on August 17, 2024

I must be a corner-case that should be fixed in https://github.com/scalalandio/chimney/blob/master/chimney-macro-commons/src/main/scala-3/io/scalaland/chimney/internal/compiletime/ExprPromisesPlatform.scala#L125 which wasn't triggered by any of the tests we have.

I reproduced the issue with Scastie, and my guess is that:

   case pink @ Playground.Color.pink =>
     val pink: Playground.Color.pink.type = pink

must be catching all values (basically being treated as case pink =>), and then this exception is justified. I am not sure though why it happens and why none of our tests are not having this issue (we explicitly test matching of every value of an enum/sealed trait to make sure it doesn't happen).

Perhaps @jchyb would know something?

from chimney.

MateuszKubuszok avatar MateuszKubuszok commented on August 17, 2024

@jchyb thank you for figuring it out!

If that's the case then either:

So to fix it we'd have to:

@saeltz would you be interested in trying to fix it?

from chimney.

MateuszKubuszok avatar MateuszKubuszok commented on August 17, 2024

Since there is nothing we can do do fix this, and the only solution I can see is fixing it in the compiler, I'll close it now.

from chimney.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.