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

Undefined reference when typing dependent annotation #21595

Closed
mbovel opened this issue Sep 15, 2024 · 7 comments
Closed

Undefined reference when typing dependent annotation #21595

mbovel opened this issue Sep 15, 2024 · 7 comments

Comments

@mbovel
Copy link
Member

mbovel commented Sep 15, 2024

The following still fails:

class dummy(b: Boolean) extends annotation.StaticAnnotation

object Test:
  def foo(elem: Int, bla: Int @dummy(elem == 0)) = bla
-- Error: try/annn2.scala:9:37 -------------------------------------------------
  9 |  def foo(elem: Int, bla: Int @dummy(elem == 0)) = bla
    |                                     ^^^^^^^^^
    |          undefined: elem.== # -1: TermRef(TermParamRef(elem),==) at typer

The initial typing of the arguments work, but when we want to create the MethodType using fromSymbols we perform a substitution. We correctly substitute TermRef(NoPrefix, elem) by a TermParamRef representing the first parameter of the MethodType, the TypeMap goes back up one level, and creates new elem.== which can be done without forcing the underlying type of elem, but when we go back up one more level and create a new elem.==(0), the TypeAssigner for Apply forces the computation of the underlying type of elem.==, which in turn requires computing the underlying type of elem, where we get NoType because we're in the middle of computing paramInfos:

override def underlying(using Context): Type = {
// TODO: update paramInfos's type to nullable
val infos: List[Type] | Null = binder.paramInfos
if (infos == null) NoType // this can happen if the referenced generic type is not initialized yet

val paramInfos: List[Type] = paramInfosExp(this: @unchecked)

To avoid this issue it seems we'd need paramInfos to be filled progressively as we compute each parameter, so the second parameter can at least safely refer to the first one.

Originally posted by @smarter in #19957 (comment)

@mbovel
Copy link
Member Author

mbovel commented Sep 15, 2024

I tried to add a notion of "temporary parameter types", that would be used in MethodType before paramInfosare properly initialized: https://github.com/mbovel/dotty/tree/mb/21595. That seems hacky, but that's the smallest changset I can think of. What else could we do? Refactor MethodType more deeply so that it computes parameters one by one? Would you use a mutable sequence for that?

@mbovel
Copy link
Member Author

mbovel commented Sep 15, 2024

@EugeneFlesselle and @natsukagami noted that we have a similar limitation with default parameters:

-- [E006] Not Found Error: 21595.scala:3:32 ------------------------------------
3 |  def foo(elem: Int, bla: Int = elem + 1) = bla
  |                                ^^^^
  |                                Not found: elem
  |
  | longer explanation available when compiling with `-explain`

A workaround for both is to use multiple parameter lists:

object Test:
  def foo(elem: Int)(bla: Int = elem + 1) = bla
class dummy(b: Boolean) extends annotation.StaticAnnotation

object Test:
  def foo(elem: Int)(bla: Int @dummy(elem == 0)) = bla

@mbovel
Copy link
Member Author

mbovel commented Sep 15, 2024

(Note also that I don't have this problem on my qualified types experiments, because I have a custom annotation with a custom representation and its own mapWith. With these, the method resolution is never re-triggered. I wondering if this could be problematic in some situations.)

@odersky
Copy link
Contributor

odersky commented Sep 17, 2024

I am more and more convinced that the whole approach of letting annotations take arbitrary trees is doomed. We'll never get it to work properly in all cases. We should seriously consider switching to annotations taking types (including constant and singleton types), not trees.

@noti0na1
Copy link
Member

I had a similar issue with integrateRT in cc.Setup (for a different reason)!

@mbovel
Copy link
Member Author

mbovel commented Nov 4, 2024

class dummy(b: Any) extends annotation.StaticAnnotation

class X:
  def foo() = 1
  def bar() = 2
  def eq(x: X) = true

class Y extends X:
  override def bar() = 2
  override def eq(x: Y) = true

def f(x: Int) = x
def g(x: String) = x
def g(x: Int) = x

object Test:
  def foo1(elem: Int, bla: Int @dummy(Array(elem))) = bla
  def foo2(elem: X, bla: Int @dummy(elem.foo())) = bla // error
  def foo3(elem: Y, bla: Int @dummy(elem.foo())) = bla // error
  def foo4(elem: X, bla: Int @dummy(elem.bar())) = bla // error
  def foo5(elem: Y, bla: Int @dummy(elem.bar())) = bla // error
  def foo6(elem: X, bla: Int @dummy(elem.eq(X()))) = bla // error
  def foo7(elem: Y, bla: Int @dummy(elem.eq(Y()))) = bla // error
  def foo8(elem: Int, bla: Int @dummy(f(elem))) = bla
  def foo9(elem: Int, bla: Int @dummy(g(elem))) = bla
  def foo10(elem: Int, bla: Int @dummy(0 == elem)) = bla
  def foo11(elem: Int, bla: Int @dummy(elem == 0)) = bla // error

@mbovel mbovel mentioned this issue Nov 8, 2024
@mbovel
Copy link
Member Author

mbovel commented Nov 8, 2024

This issue is a duplicate of #17242.

@mbovel mbovel closed this as not planned Won't fix, can't repro, duplicate, stale Nov 8, 2024
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

4 participants