question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Can't define JS facades with wildcards

See original GitHub issue

Compiler 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:closed
  • Created 2 years ago
  • Comments:6 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
japgollycommented, Apr 16, 2021

@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.

0reactions
smartercommented, Apr 19, 2021

type F[A] = List[A]; type G = F[?] should have no problems.

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Is it possible to use wildcards in webpack's require.context ...
I want to allow for .story.js files outside of .stories/ directories, and have Webpack ignore them. Is this possible? What am I missing?...
Read more >
Can I use wildcards when adding a snapshot to an event?
I have a specific use case but can't seem to get it to work. If I define the snapshot using the pointer tool,...
Read more >
How to Create Multiple Wildcard Routes at the Same Root ...
For our example of /blog-slug-url we would first define the route in the routes file, defining the route with the static function get()...
Read more >
Beyond Façade : Pattern Matching for Natural Language ...
The wildcard * binds to any non-zero number of words, creating patterns matching many inputs. Problems with authoring arise instantly. Matching ...
Read more >
Problem with using wildcards in Object finding
I am using a function (javascript) to find objects in the application by checking the properties of an object (using Find & FindAllChildren)...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found