Can't define JS facades with wildcards
See original GitHub issueCompiler version
3.0.0-RC1
Minimized code
This is just a small part of the Scala JS facades for React. It’s quite common for JS facades to be type aliases in addition to traits and classes.
type ComponentClass [P <: js.Object, S <: js.Object] = js.Function1[P, React.Component[P, S]] with HasDisplayName
type ComponentClassP[P <: js.Object] = ComponentClass[P, _ <: js.Object]
type ComponentClassUntyped = ComponentClass[_ <: js.Object, _ <: js.Object]
Output
[warn] -- [E043] Type Migration Warning: /home/golly/projects/public/scalajs-react/core/src/main/scala/japgolly/scalajs/react/raw/React.scala:85:55
[warn] 85 |type ComponentClassP[P <: js.Object] = ComponentClass[P, _ <: js.Object]
[warn] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[warn] |unreducible application of higher-kinded type [P <: scalajs.js.Object, S <: scalajs.js.Object] =>>
[warn] | scalajs.js.Function1[P, japgolly.scalajs.react.raw.React.Component[P, S]]
[warn] | & japgolly.scalajs.react.raw.HasDisplayName to wildcard arguments
[warn] -- [E043] Type Migration Warning: /home/golly/projects/public/scalajs-react/core/src/main/scala/japgolly/scalajs/react/raw/React.scala:86:55
[warn] 86 |type ComponentClassUntyped = ComponentClass[_ <: js.Object, _ <: js.Object]
[warn] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[warn] |unreducible application of higher-kinded type [P <: scalajs.js.Object, S <: scalajs.js.Object] =>>
[warn] | scalajs.js.Function1[P, japgolly.scalajs.react.raw.React.Component[P, S]]
[warn] | & japgolly.scalajs.react.raw.HasDisplayName to wildcard arguments
Workaround
I have to manually expand all the type aliases.
type ComponentClass [P <: js.Object, S <: js.Object] =
js.Function1[P, React.Component[P, S]] with HasDisplayName
type ComponentClassP[P <: js.Object] =
js.Function1[P, React.Component[P, _ <: js.Object]] with HasDisplayName
type ComponentClassUntyped =
js.Function1[_ <: js.Object, React.Component[_ <: js.Object, _ <: js.Object]] with HasDisplayName
which is very troublesome because clearly Scala 3 supports the intention of the code - it just doesn’t expand wildcards through type aliases.
Expectation
This has nothing to do with existential types which have been removed from Scala 3. This is about wildcards so I’m expecting this to compile without warnings. Type aliases and wildcards shouldn’t be mutually-exclusive.
Proposal 1
I expect that if I pass in a wildcard to a type-alias, and that type is only used once, then it should be a matter of simple substitution and no contention:
type F[A] = List[A]
type G = F[?] // via simple substitution, it should be the same as: type G = List[?]
If a type alias arg is used more than once, @odersky has previously said that the expansion requires existential types
type F[A] = (List[A], List[A])
type G = F[?] // should expand to (List[A], List[A]) {for some A=?}
but as far as I can tell, simple substitution would work fine here too because if F
were manually expanded using wildcards, it behaves like this:
type G = (List[?], List[?])
( List[Int](), List[Unit]() ) : G // this passes despite the Lists being of different types
// they both satisfy the same bounds specified by the wildcards.
// (List[A], List[A]) {for some A=?} ≡ (List[?], List[?])
The relationship between the two occurrences of A
in F
being the same doesn’t hold for wildcards (which just moves the bounds). If type aliases were to use simple substitute and allow wildcards I think it would behave the same. I don’t see how we lose anything.
Proposal 2
If we can’t have simple substitution, can we have another means of doing this valid thing? After all, it’s quite common in JS facades. Maybe we could have *
or something instead of ?
to allow us to use wildcards with type aliases? Meaning type G=F[?]
would warn about existential types but type G=F[*]
is a user very explicitly specifying a wildcard and expecting the simple substitution.
At the end of the day, manually expanding type aliases everywhere is frustrating but most importantly, carries the risk that manually-expanded aliases will go out of sync. I think we need some kind of solution in place even if it’s not one of the above two suggestions. 🙂
Issue Analytics
- State:
- Created 2 years ago
- Comments:6 (6 by maintainers)
@sjrd Yes I could provide minifications that don’t rely on
scalajs.js
, but the ability to use type aliases and wildcards is often crucial to writing JS facades. I just wanted to provide more context to why this is important, show some of the real situations, and demonstrate why a simple syntax fix by a facade author often isn’t feasible.Right, I just tried it and it does work: https://scastie.scala-lang.org/kh0lb3RLQkyAL1JzNdY0WA, so I don’t know what issue you’re facing.