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 reference of extension method when exported and used at toplevel #13669

Closed
soronpo opened this issue Oct 3, 2021 · 8 comments · Fixed by #13719
Closed

Cyclic reference of extension method when exported and used at toplevel #13669

soronpo opened this issue Oct 3, 2021 · 8 comments · Fixed by #13719

Comments

@soronpo
Copy link
Contributor

soronpo commented Oct 3, 2021

Compiler version

v3.1.0-RC2

Minimized code

https://scastie.scala-lang.org/upOXnTlLTgaIZoVUhlsMGg

trait MyExtensions:
  extension (lhs: Int) def bash: Unit = {}

object MyExtensions extends MyExtensions

object Internal:
  export MyExtensions.*
  val works = 1.bash

export MyExtensions.*
val fails = 1.bash

Output

-- [E046] Cyclic Error: test.scala:2:18 -------------------------------------------
2 |  extension (lhs: Int) def bash: Unit = {}
  |                  ^
  |                  Cyclic reference involving method bash

longer explanation available when compiling with `-explain`
-- [E008] Not Found Error: test.scala:11:14 ---------------------------------------
11 |val fails = 1.bash
   |            ^^^^^^
   |            value bash is not a member of Int

Expectation

No error.
Note: if used in scastie in Worksheet mode, this example compiles successfully.

@odersky
Copy link
Contributor

odersky commented Oct 4, 2021

I don't see a reason to fix this. Cyclic References are a possibility. So to classify as a bug you'd have to argue that the particular CyclicReference should not happen and can be avoided. You can ask why it does work if the export is in an explicit object. That's a good question. to ask and answer

@odersky odersky closed this as completed Oct 4, 2021
@soronpo
Copy link
Contributor Author

soronpo commented Oct 4, 2021

We want users to have fun coding in Scala is a good enough reason for fixing, IMO. Yet another inconsistent limitation that causes an error that gives no clue to the actual reason for the error is not the way to achieve it.

@odersky
Copy link
Contributor

odersky commented Oct 4, 2021

Right, but my time is limited and I do already the best I can and more. If people want this fixed then they have to step up and fix it! Right now I see a steady increase of issues some of which are marginal. This means that important issues are more likely to be overlooked.

@smarter
Copy link
Member

smarter commented Oct 4, 2021

As far as I can tell the cycle looks like this:

  1. We enter the definition of the bash extension method
  2. This requires typechecking the type of its parameter lhs: Int
  3. There might be a definition called Int in the enclosing scope, so lets check all its definitions!
  4. Wait, the enclosing scope contains export MyExtensions.*, so we need to complete that first to see if it contains any definition called Int
  5. At that point we try to generate the export forwarder for bash, and get a cycle completing the type of bash which we're still in the process of entering.

But I'm not sure why it compiles fine if the method is defined in the object directly instead of in a parent trait:

object MyExtensions:
  extension (lhs: Int) def bash: Unit = {}

export MyExtensions.bash
val fails = 1.bash

It seems like that changes the order in which we complete things (export -> definition rather than definition -> export -> definition)

@soronpo
Copy link
Contributor Author

soronpo commented Oct 4, 2021

Right, but my time is limited and I do already the best I can and more. If people want this fixed then they have to step up and fix it! Right now I see a steady increase of issues some of which are marginal. This means that important issues are more likely to be overlooked.

Keeping an issue open does not mean YOU need to fix it. If it's something that we can agree requires a fix, then keep the issue open. If it's a low priority, then just tag it as backlog or low-priority. Closing it means that it's not a problem, and reduces the likelihood of someone finding it before re-reporting the same problem. If it is indeed not fixable then it still means we either reflect this in the spec/documentation and/or produce better error. Closing it is just "dirt-under-the-rug", IMO.

@odersky
Copy link
Contributor

odersky commented Oct 4, 2021

I agree it's not me who has to fix it. But I see nobody else doing it either at present. And the more open bugs the less efficient people signing up to fix them will be. Now this one is an issue where it is not clear whether it is a bug or not. It's an interesting question why one produces a cycle and the other does not. But in general cycles are to be expected for this kind of code. So I prefer we do not clog the bug pipeline and our own limited mental capacity to figure it out. If someone wants to do it on the side, great! But until we known more, it's a puzzle, not a bug that needs fixing.

@smarter smarter linked a pull request Oct 8, 2021 that will close this issue
@smarter
Copy link
Member

smarter commented Oct 8, 2021

As a workaround, you can move the object definition ahead of the trait definition:

object MyExtensions extends MyExtensions
trait MyExtensions:
  extension (lhs: Int) def bash: Unit = {}

export MyExtensions.*
val fails = 1.bash

I'm also reopening since I have a tentative fix: #13719

@smarter smarter reopened this Oct 8, 2021
@soronpo
Copy link
Contributor Author

soronpo commented Oct 8, 2021

Thank you very much @smarter and @odersky !

@Kordyjan Kordyjan added this to the 3.1.1 milestone Aug 2, 2023
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.

4 participants