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

Splat type vars in inheritance #3649

Open
david50407 opened this issue Dec 6, 2016 · 6 comments · May be fixed by #10240
Open

Splat type vars in inheritance #3649

david50407 opened this issue Dec 6, 2016 · 6 comments · May be fixed by #10240
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic

Comments

@david50407
Copy link
Contributor

david50407 commented Dec 6, 2016

Submitting bugs

Make sure to review these points before submitting issues - thank you!

Thank you, too 😄

Code 1:

class Bar(T, T2)
  def go
    puts T.to_s, T2.to_s
  end
end

class Foo(*T) < Bar(*T)
  def go
    puts T.to_s
    super
  end
end

Foo(Int32, Int64).new.go
Foo(Int32).new.go

Result:

Error in line 7: wrong number of type vars for Bar(T, T2) (given 1, expected 2)

Code 2:

class Bar(T, T2)
  def go
    puts T.to_s, T2.to_s
  end
end

class Foo(*T) < Bar(*T, Int16)
  def go
    puts T.to_s
    super
  end
end

Foo(Int32, Int64).new.go
Foo(Int32).new.go

Result:

Tuple(Int32, Int64)
Int32
Int64
Tuple(Int32)
Int32
Int16

Question

In code 1, if splat type var is not support in crystal, this should be syntax error.
In code 2, if splat type var is supported, what is the rule of the splat type var?

Crystal version

  • play.crystal-lang.org 0.19.4
  • 0.20.0
  • 0.20.0+74 [fbdc736]
@asterite
Copy link
Member

asterite commented Dec 7, 2016

class Foo(*T) means that you can pass any number of type arguments to Foo, which is actually not true when you inherit from Bar. Bar accepts just two arguments, so Foo must also accept just two arguments (or, well, a different type, but if you are matching T* with T* then they must be the same).

Probably both snippets are just compiler bugs, I never imagined a use case for that (splats in types are not complete yet in the compiler)

What are you trying to do? :-)

@david50407
Copy link
Contributor Author

@asterite I'm working on #2803 , so I just try this for understanding how splat type var works in other similar situations. And I find these questions.

Actually, I cannot imagined the use case for this, too. 😆

@david50407
Copy link
Contributor Author

@asterite if this is actually a compiler bug, I can send a PR for fixing this.

@asterite
Copy link
Member

I don't think < Bar(*T, Int16) is correct, that should be a compile-error right away.

In any case, this is super minor, I can't think of any use case for that code.

@HertzDevil
Copy link
Contributor

HertzDevil commented Jan 10, 2021

If there are abstract defs in Bar that use the individual type vars:

abstract class Bar(T, T2)
  abstract def get(key : T) : T2
  abstract def set(key : T, value : T2)
end

Then it is pretty much impossible to implement them in Foo:

class Foo(*T) < Bar(*T)
  def get(key : K) forall K
    {% if K <= T[0] %} # bad, obscures overload resolution
    {% end %}
  end

  def get(key : T[0]) : T[1] # incorrect, these form `StaticArray`s
  end

  def get(key : typeof(0.unsafe_as(T)[0])) : typeof(0.unsafe_as(T)[1]) # `typeof` not allowed here
  end

  def set(*args : *T) # technically allowed, but also bad
  end
end

This shortcoming is huge enough that I'm inclined to make both snippets compile-time errors; that is, a splat expansion must correspond to a splat parameter in the included/inherited type. If it's possible to access members of a type var splat in these contexts then we could revisit this feature.

However, if Bar does have a type splat, then scenarios like below should be supported:

class Bar(*T)
  def self.f
    T
  end
end

class Foo(*U) < Bar(*U)
end

Foo(Int32, String).f # expected: Tuple(Int32, String)
                     # got:      Tuple(*U)

This part is covered by #10232.

@HertzDevil
Copy link
Contributor

My bad, Bar can actually be implemented as follows, by simply not shadowing the type parameter names in the superclass / included module:

abstract class Bar(T, T2)
  abstract def get(key : T) : T2
  abstract def set(key : T, value : T2)
end

class Foo(*T3) < Bar(*T3)
  def get(key : T) : T2
    raise ""
  end

  def set(key : T, value : T2)
  end
end

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
4 participants