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

False positive recursive alias during overload ordering #11678

Open
HertzDevil opened this issue Jan 2, 2022 · 1 comment · May be fixed by #11840
Open

False positive recursive alias during overload ordering #11678

HertzDevil opened this issue Jan 2, 2022 · 1 comment · May be fixed by #11840
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic

Comments

@HertzDevil
Copy link
Contributor

HertzDevil commented Jan 2, 2022

The following code reports an improper recursive alias even though there are none:

class Foo1; end
class Foo2 < Foo1; end
class Foo3 < Foo1; end

class Foo1
  alias Bar = Foo2::Bar | Foo3::Bar # Error: infinite recursive definition of alias Foo1::Bar
end

class Foo2 < Foo1
  class Bar; end
  def foo(other : Foo1::Bar); end
  def foo(other : String); end
end

class Foo3 < Foo1
  class Bar; end
end

It happens because the overloads of Foo2#foo need to be ordered, and in doing so the compiler looks up Foo1::Bar's definition as soon as the String overload is defined. At this point, Foo3 is indeed defined, and it inherits constants from Foo1, so Foo3::Bar is exactly Foo1::Bar, which explains why Foo1::Bar refers to itself. Similarly, an "undefined constant" error can be triggered this way by not forward declaring any types:

class Foo1
  alias Bar = Foo2::Bar | Foo3::Bar # Error: undefined constant Foo3::Bar
end

class Foo2 < Foo1
  class Bar; end
  def foo(other : Foo1::Bar); end # okay, `Foo1` is defined up to this point
  def foo(other : String); end
end

class Foo3 < Foo1
  class Bar; end
end

I discovered this while trying to do this seemingly harmless refactor:

# src/float.cr

struct Float64
  # `Foo1::Bar` corresponds to `Number::Primitive`
  # note that primitive number types are forward declared by the compiler itself
  Number.expand_div [Int::Primitive], Float64
  Number.expand_div [Float32], Float64
end

A workaround is to also forward declare Bar1 before any methods are defined. On the other hand, making these examples work means that the compiler has to defer overload ordering to a later stage (contrast with #4897); resolving the aliases "beforehand" is not enough, because both aliases and defs are declared as they are read during the top-level phase.

@HertzDevil HertzDevil added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic labels Jan 2, 2022
@HertzDevil
Copy link
Contributor Author

HertzDevil commented Jan 4, 2022

Here is another variant also triggered by the inheritance of namespaced constants:

class Foo1
  class Bar; end
end

class Foo2 < Foo1; end

def foo(x : Foo1::Bar); end
def foo(x : Foo2::Bar); end
# at this point `Foo2::Bar` is identical to `Foo1::Bar`, so this is a
# redefinition and the first overload disappears

class Foo2::Bar; end

foo(Foo1::Bar.new) # Error: no overload matches 'foo' with type Foo1::Bar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant