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

Opaque type weirdness: (self$proxy1 : Any) Required: BigDecimalNewType_this.Type #14656

Closed
Swoorup opened this issue Mar 10, 2022 · 6 comments · Fixed by #14668
Closed

Opaque type weirdness: (self$proxy1 : Any) Required: BigDecimalNewType_this.Type #14656

Swoorup opened this issue Mar 10, 2022 · 6 comments · Fixed by #14668

Comments

@Swoorup
Copy link

Swoorup commented Mar 10, 2022

Opaque types possessing black magic.

It appears that adding 3 or more opaque type numeric causes the code to fail to compile. However here, uncommenting in Line 15 makes the code compile, which is odd, as it is seemingly a random opaque type declaration. Adding 2 Amounts is fine, the issue appears when adding 3 Amounts

Compiler version

3.1.1

Minimized code

trait Newtype[Src]:
  opaque type Type = Src

transparent trait BigDecimalNewType extends Newtype[BigDecimal]:
  override opaque type Type = BigDecimal

  inline def apply(inline dec: BigDecimal): Type = dec

  extension (self: Type)
    inline def value: BigDecimal = self
    inline def +(inline y: Type): Type = self.value + y.value

type Amount = Amount.Type
object Amount extends BigDecimalNewType {
  // opaque type Blahblahblah = BigDecimal
}

case class Fees(
  exchange: Amount,
  slippage: Amount,
  bribe: Amount
):
  def calculateTotalFees: Amount = exchange + slippage + bribe

@main def main(): Unit = 
  val fees: Fees = Fees(Amount(1),Amount(2), Amount(2))
  val totalFees: Amount = fees.calculateTotalFees

  println(s"totalFees: ${totalFees}")

Scastie link: https://scastie.scala-lang.org/MLbciD4bQtysh7mVlYy1ig

Output

Found:    (self$proxy1 : Any)
Required: BigDecimalNewType_this.Type

Expectation

Compile regardless of Line 15 in the code.

@Swoorup Swoorup added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 10, 2022
@Swoorup Swoorup changed the title Opaque type weirdness Opaque type weirdness: (self$proxy1 : Any) Required: BigDecimalNewType_this.Type Mar 10, 2022
@prolativ
Copy link
Contributor

I'm wondering if what you're trying to do here should be possible at all #14659

@prolativ prolativ added area:opaque-types and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 10, 2022
@Swoorup
Copy link
Author

Swoorup commented Mar 10, 2022

I'm wondering if what you're trying to do here should be possible at all #14659

I don't see why it should not be possible? Although your example (opaque alias of String overriding Int) definitely looks like it should be an error. It would be nice if there was some mechanism to pass transparentness of opaque type in extension/implementation of trait (as trait could also be used as mixins).

But regardless this current issue still persist even if you avoid override of opaque type:

trait Newtype[Src]:
  opaque type Type = Src

transparent trait BigDecimalNewType extends Newtype[BigDecimal]:
  inline def apply(inline dec: BigDecimal): Type = dec.asInstanceOf[Type]

  extension (self: Type)
    inline def value: BigDecimal = self.asInstanceOf[BigDecimal]
    inline def +(inline y: Type): Type = (self.value + y.value).asInstanceOf[Type]

type Amount = Amount.Type
object Amount extends BigDecimalNewType {
  // Uncommenting this fixes the issue.
  // opaque type Blahblahblah = BigDecimal
}

case class Fees(
  exchange: Amount,
  slippage: Amount,
  bribe: Amount
):
  def calculateTotalFees: Amount = exchange + slippage + bribe

@prolativ
Copy link
Contributor

Ok, so that's a separate issue though it might have a similar genesis to #14653.
I minimized it even further

trait BigDecimalNewType:
  opaque type Type = BigDecimal
  def apply(value: BigDecimal): Type = value
  extension (self: Type)
    def value: BigDecimal = self
    inline def +(y: Type): Type = apply(self.value + y.value)

object Amount extends BigDecimalNewType {
  val x = Amount(0)
  val y = (x + x) + x
}

but here defining an additional unused opaque type doesn't make everything magically compile

@Swoorup
Copy link
Author

Swoorup commented Mar 11, 2022

but here defining an additional unused opaque type doesn't make everything magically compile

Yeah weird. Adding override opaque type Type = BigDecimal in the Amount companion object does though. Might be yet another issue. 😄

@prolativ
Copy link
Contributor

That particular problem has just been fixed #14665

@odersky
Copy link
Contributor

odersky commented Mar 11, 2022

Reduced further to:

trait BigDecimalNewType:
  opaque type Type = BigDecimal
  def apply(value: BigDecimal): Type = value
  def value(self: Type): BigDecimal = self
 inline def plus(self: Type, y: Type): Type = apply(value(self) + value(y))

object Amount extends BigDecimalNewType {
  val x = Amount(0)
  val y0 = plus(x, x)
  val y = plus(y0, plus(x, x))
}

Problem goes away if inline is dropped. Problem also exists if inline is replaced with transparent inline.
Problem also goes away if BigDecimalNewType is changed to an object, as in:

object BigDecimalNewType:
  opaque type Type = BigDecimal
  def apply(value: BigDecimal): Type = value
  def value(self: Type): BigDecimal = self
  transparent inline def plus(self: Type, y: Type): Type = apply(value(self) + value(y))

object Amount {
  import BigDecimalNewType.*
  val x = BigDecimalNewType(0)
  val y0 = plus(x, x)
  val y = plus(y0, plus(x, x))
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants