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

This-types are inferred and passed to blackbox macros outside of the valid scope for a this-type #16107

Closed
neko-kai opened this issue Sep 26, 2022 · 3 comments
Assignees
Milestone

Comments

@neko-kai
Copy link
Contributor

neko-kai commented Sep 26, 2022

Compiler version

3.2.1-RC2 and 3.1.3

Minimized code

// file exampleMacro.scala

package example

import scala.quoted.{Quotes, Expr, Type}

inline def printTypeOf[A](a: A): Unit = ${ impl[A] }

private def impl[A: Type](using quotes: Quotes): Expr[Unit] = {
  import quotes.reflect.*
  '{ println(${ Expr(TypeRepr.of[A].toString) }) }
}
// file example.scala
package example

object ThisTypeObj {
  type T >: Int
  def thisTypeFn: T = 1
}

@main def example: Unit = {
  printTypeOf(ThisTypeObj.thisTypeFn) // bad, ThisType is invalid outside of ThisTypeObj definition
                                      // TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class example)),module class ThisTypeObj$)),type T)
  printTypeOf(ThisTypeObj.thisTypeFn: ThisTypeObj.T) // ok
                                                     // TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class example)),object ThisTypeObj),type T
}

Output

TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class example)),module class ThisTypeObj$)),type T)
TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class example)),object ThisTypeObj),type T)

Expectation

Expected the following output:

TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class example)),object ThisTypeObj),type T)
TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class example)),object ThisTypeObj),type T)

Essentially the printTypeOf macro receives an inferred TypeRef for a type ThisTypeObj.this.T that's unwriteable and invalid at the point of use, because def example is not inside the definition of ThisTypeObj. Only writing an explicit type ascription obtains a more valid TypeRef for a writeable type ThisTypeObj.T

This behavior is unexpected, as we assume that this-types can only be inferred/found in scopes where they are valid/writeable. Scala 2 behaved that way, where an expression passed to the macro could not contain ThisType's for a foreign this that is not the outer scope of the expression.

This code is a minimized version of real library code in https://github.com/izumi/izumi which we're trying to port to Scala 3

@neko-kai neko-kai added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Sep 26, 2022
@dwijnand dwijnand added stat:needs triage Every issue needs to have an "area" and "itype" label area:metaprogramming:quotes Issues related to quotes and splices and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Sep 30, 2022
@nicolasstucki
Copy link
Contributor

nicolasstucki commented Nov 15, 2022

This seems to not only be an issue with macros.

object ThisTypeObj:
  type T
  def thisTypeFn: T = ???
  def test1 =
    infer(ThisTypeObj.thisTypeFn)
    infer(??? : ThisTypeObj.T)
    infer[ThisTypeObj.T](???)
    infer(??? : T)
    infer[T](???)

def infer[A](a: A): Unit = ???

def test2: Unit =
  infer(ThisTypeObj.thisTypeFn) // leaks ThisTypeObj$.this.T
  infer(??? : ThisTypeObj.T)
  infer[ThisTypeObj.T](???)

Compiling this code with -Xprint:typer -Yprint-debug shows this inconsistent inferred type

package <root>.this.<empty> {
  final lazy module val ThisTypeObj: <empty>.this.ThisTypeObj$ =
    new <empty>.this.ThisTypeObj$.<init>()
  final module class ThisTypeObj$() extends Object.<init>() {
    this: <empty>.this.ThisTypeObj.type =>
    type T >: scala.this.Nothing <: scala.this.Any
    def thisTypeFn: ThisTypeObj$.this.T = scala.this.Predef.???
    def test1: scala.this.Unit(inf) =
      {
        <empty>.this.Test_2$package.infer[ThisTypeObj$.this.T^(inf)](
          <empty>.this.ThisTypeObj.thisTypeFn)
        <empty>.this.Test_2$package.infer[<empty>.this.ThisTypeObj.T^(inf)](
          scala.this.Predef.??? :<empty>.this.ThisTypeObj.T)
        <empty>.this.Test_2$package.infer[<empty>.this.ThisTypeObj.T](
          scala.this.Predef.???)
        <empty>.this.Test_2$package.infer[ThisTypeObj$.this.T^(inf)](
          scala.this.Predef.??? :ThisTypeObj$.this.T)
        <empty>.this.Test_2$package.infer[ThisTypeObj$.this.T](
          scala.this.Predef.???)
      }
  }
  final lazy module val Test_2$package: <empty>.this.Test_2$package$ =
    new <empty>.this.Test_2$package$.<init>()
  final module class Test_2$package$() extends Object.<init>() {
    this: <empty>.this.Test_2$package.type =>
    def infer[A >: scala.this.Nothing <: scala.this.Any](a: A):
      <root>.this.scala.Unit = scala.this.Predef.???
    def test2: <root>.this.scala.Unit =
      {
        <empty>.this.Test_2$package.infer[ThisTypeObj$.this.T^(inf)](
          <empty>.this.ThisTypeObj.thisTypeFn)
        <empty>.this.Test_2$package.infer[<empty>.this.ThisTypeObj.T^(inf)](
          scala.this.Predef.??? :<empty>.this.ThisTypeObj.T)
        <empty>.this.Test_2$package.infer[<empty>.this.ThisTypeObj.T](
          scala.this.Predef.???)
      }
  }
}

@smarter is this an issue with avoidance or something else?

@dwijnand dwijnand removed the stat:needs triage Every issue needs to have an "area" and "itype" label label Nov 16, 2022
@dwijnand
Copy link
Member

Just jotting down some notes.

Those type variables are instantiated when Typer calls interpolateTypeVars. When that calls TypeVar.instantiate -> TypeComparer.instanceType that does run through widenInferred. However, I don't know whether that has access to differentiating Nico's test1 and test2 situations to know whether to map to a TermRef of the module. Which reminds me of this old comment: "doing whatever it takes to introduce contexts to Types is that lubs currently cannot be calculated correctly", which was linked at the top of #4228.

Ultimately I don't see this as much of a bug. A "This" of a module class, when not in the module, isn't ambiguous. It just needs to be converted. Might come back to seeing if this is possible.

@dwijnand dwijnand added area:infer and removed area:metaprogramming:quotes Issues related to quotes and splices labels Nov 16, 2022
@Kordyjan Kordyjan added this to the Future versions milestone Dec 12, 2022
@Kordyjan Kordyjan modified the milestones: Future versions, 3.3.1-RC1 Jan 16, 2023
neko-kai added a commit to zio/izumi-reflect that referenced this issue Apr 24, 2023
…la3#16107 - check if this-type prefix is present in the owner chain
neko-kai added a commit to zio/izumi-reflect that referenced this issue Apr 24, 2023
* Fix 'weak' abstract type detection on Scala 3 - work around scala/scala3#16107 - check if this-type prefix is present in the owner chain

* fix 2.11 build
@dwijnand
Copy link
Member

As I mentioned, I'm not too convinced this is much of a bug. There two shapes for the same type - the module. So, rather than trying to make this come out of the compiler, I'd say the consuming code should "just" handle them both.

@dwijnand dwijnand closed this as not planned Won't fix, can't repro, duplicate, stale May 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants