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

Cyclic errors when using exports #17076

Open
doofin opened this issue Mar 9, 2023 · 14 comments
Open

Cyclic errors when using exports #17076

doofin opened this issue Mar 9, 2023 · 14 comments

Comments

@doofin
Copy link

doofin commented Mar 9, 2023

This is a very strange problem that only shows occasionally, I have no idea how to reliably reproduce it.
updates: reproduced in #17076 (comment) and #17076 (comment)

Compiler version

3.2.2

Minimized code

it can't be reproduced,that's why it's so strange. I copied this snippet to another project without giving any errors.

  sealed trait Expr[w <: Int]
  case class BinOp[w <: Int](a: Expr[w], b: Expr[w], nm: String) extends Expr[w]

Output

metals ide gives error below,and sbt also gives same error message:

image

the error message is

Overloaded or recursive constructor BinOp needs return type
Something's wrong: missing original symbol for type tree 

Sometimes, it will give errors like "Cyclic reference involving val ..."

Expectation

code should compile

@doofin doofin added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 9, 2023
@nicolasstucki nicolasstucki added stat:cannot reproduce and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 10, 2023
@nicolasstucki
Copy link
Contributor

Could this be related to the incremental compilation?

@nicolasstucki
Copy link
Contributor

@doofin when you get the error, have you tried to compile it manually from the terminal? Just to see if the error happens in general or within the current state of bloop.

@doofin
Copy link
Author

doofin commented Mar 10, 2023

@doofin when you get the error, have you tried to compile it manually from the terminal? Just to see if the error happens in general or within the current state of bloop.

I tried to compile with sbt manually (I have also clean/cleanFiles before compile) , and it's the same error

I feel it might be related to incremental compilation, but this scenario seems to suggest it's more than that

trying to search for "Overloaded or recursive constructor"(does that imply type constructors needs kind annotation?) gives no result in dotty codebase,there's only "Overloaded or recursive methods".

@prolativ
Copy link
Contributor

@doofin would it be possible for you to provide the entire codebase of the project (assuming it's not confidential)? If not then maybe you could try creating a copy of the project and gradually removing portions of code until the problem stops occurring?

@doofin
Copy link
Author

doofin commented Mar 13, 2023

thanks @smarter to help me pinpoint the problem, it happens when some methods are not explicitly given type annotations like below:

extension [w <: Int](x: Expr[w]) {
  def +(oth: Expr[w]) = BinOp(x, oth, "+")
  def *(oth: Expr[w]) = BinOp(x, oth, "*")
  def -(oth: Expr[w]) = BinOp(x, oth, "-")
  def |(oth: Expr[w]) = BinOp(x, oth, "|")
  def &(oth: Expr[w]) = BinOp(x, oth, "&")
}

However, I copied this snippet to another project but still can't reproduce it. Will meet with @smarter for possible better error message when this happens.

@doofin
Copy link
Author

doofin commented Mar 16, 2023

Hi @prolativ I tried but it might be a bit hard to reduce it to minimul. The project might be open sourced in the future but is currently private. I have invited you to the project and feel free to take a look at doofin/dependentChisel@101cf6d

@smarter
Copy link
Member

smarter commented Mar 16, 2023

I can reproduce the errors with:

object A {
  import B.*
  def foo(foo: Foo[Int]) = foo
}

object B {
  import C.*
  trait Expr[T <: Int]
  case class Foo[T <: Int](x: Any) extends Expr[T]
  def foo = Foo(0)
}

object C extends D.Foo[Int](0)

object D {
  export B.*
}
-- Error: try/i17076/A.scala:9:30 ----------------------------------------------
9 |  case class Foo[T <: Int](x: Any) extends Expr[T]
  |                              ^^^
  |                  Something's wrong: missing original symbol for type tree
-- [E044] Cyclic Error: try/i17076/A.scala:9:2 ---------------------------------
9 |  case class Foo[T <: Int](x: Any) extends Expr[T]
  |  ^
  |  Overloaded or recursive constructor Foo needs return type

The use of object C extends D.Foo[Int](0) is arguably problematic since we import C.* in the scope where we define Foo itself, but there isn't anything equivalent in the original codebase, so there's probably still something missing to trigger the problem in the same way it's triggered in the original codebase.

@smarter smarter changed the title Something's wrong: missing original symbol for type tree Cyclic errors when using exports Mar 16, 2023
@prolativ
Copy link
Contributor

@smarter I spent a few hours yesterday minimizing that and I almost got to this point but you turned out to be faster 😆

@prolativ
Copy link
Contributor

Minimized slightly further:

  • got rid of trait Expr
  • got rid of wildcard exports
  • got rid of import B.*
object A {
  def bar(x: B.Foo[Int]) = x
}

object B {
  import C.*
  case class Foo[T <: Int](x: Any)
  def foo = Foo(0)
}

object C extends D.Foo[Int](0)

object D {
  export B.foo
  type Foo[T <: Int] = B.Foo[T]
}

@odersky
Copy link
Contributor

odersky commented Apr 25, 2023

We clearly should not complain about a missing return type in a constructor. We should fall back to the generic "cyclic reference involving...." error message instead.

That said, this looks like a legit cyclic reference error, no?

  • To process the export, you need to know the type of B.foo.
  • To know the type you need to process the RHS of B.foo.
  • To process the RHS you need to typecheck class B.Foo
  • To typecheck class B.Foo you need to analyze the import import C.* (for instance, it might define a type Int, which would take precedence over scala.Int.
  • To analyze the import, you need to find the members of C.
  • To find the members of C you need to analyze its parent, D.Foo
  • To analyze that parent you need to find the members of D.
  • To find the members of D you need to analyze the export.

There's your cycle.

@robmwalsh
Copy link

We clearly should not complain about a missing return type in a constructor. We should fall back to the generic "cyclic reference involving...." error message instead.

That said, this looks like a legit cyclic reference error, no?

  • To process the export, you need to know the type of B.foo.
  • To know the type you need to process the RHS of B.foo.
  • To process the RHS you need to typecheck class B.Foo
  • To typecheck class B.Foo you need to analyze the import import C.* (for instance, it might define a type Int, which would take precedence over scala.Int.
  • To analyze the import, you need to find the members of C.
  • To find the members of C you need to analyze its parent, D.Foo
  • To analyze that parent you need to find the members of D.
  • To find the members of D you need to analyze the export.

There's your cycle.

FWIW I think cyclic errors should be displayed like your comment (including "There's your cycle" ;) )

@odersky
Copy link
Contributor

odersky commented May 22, 2023

FWIW I think cyclic errors should be displayed like your comment (including "There's your cycle" ;) )

Yes, this would be great! I don't yet see an easy way to do it, though. Essentially, the compiler would have to keep a log of everything it does which would have to be inspected in case of a Cyclic Reference. That looks expensive, in both time and code complexity...

@robmwalsh
Copy link

FWIW I think cyclic errors should be displayed like your comment (including "There's your cycle" ;) )

Yes, this would be great! I don't yet see an easy way to do it, though. Essentially, the compiler would have to keep a log of everything it does which would have to be inspected in case of a Cyclic Reference. That looks expensive, in both time and code complexity...

I don't think recording all steps the compiler takes would be necessary, wouldn't we just need the steps that the cyclic reference check does? Couldn't this be done in a similar way to zio 2.x's stack reification? If you're unfamiliar with it, basically it tries to make progress recursively in a try catch block (it looks like the implementation for checking cyclic references is already doing this) and if it needs to yield, it throws a ReifyStack exception which contains a ChunkBuilder. This is caught at every level of recursion, adding the effect to the ChunkBuilder and rethrowing the ReifyStack exception. When it gets to the first try/catch, the exception contains the whole stack which is turned into a chunk and sent back to the scheduler. I think a similar approach may be used here which means basically no overhead unless there's a cyclic error. Do you think this approach may be feasible or am I missing something here?

@odersky
Copy link
Contributor

odersky commented May 22, 2023

We do similar stack reification when we emit "a recurring operation is" diagnostics. But we can't directly use it for cyclic references (at least not as a default) since we are not allowed to unwind the stack to the root of the problem. Instead, we continue reporting close to where the cyclic reference was detected.

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

6 participants