Skip to content

Type parameter inference is too eager to widen union types #4867

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

Closed
smarter opened this issue Jul 30, 2018 · 7 comments
Closed

Type parameter inference is too eager to widen union types #4867

smarter opened this issue Jul 30, 2018 · 7 comments

Comments

@smarter
Copy link
Member

smarter commented Jul 30, 2018

From https://stackoverflow.com/questions/51575183/dotty-seq-mapping-to-union:

object UnionMapping {
  private def parse(string: String): Int | Double = {
    if(string.contains("."))
      string.toDouble
    else
      string.toInt
  }

  def test_number = {
    val strings: Seq[String] = Seq("123", "2.0", "42")
    // Works
    val asdf: Seq[AnyVal] = strings.map(parse(_))
    // Fails to compile
    val union: Seq[Int | Double] = strings.map(parse(_))
  }
}

There's enough hints here that type inference should be able to do the right thing and not widen Int | Double to AnyVal.

@abeln
Copy link
Contributor

abeln commented Jul 31, 2018

@smarter I'd be interested in learning a bit about the type parameter inference. If this bug seems somewhat accessible, can you point me to where in the code I should be looking at for this change? Thanks!

@smarter
Copy link
Member Author

smarter commented Jul 31, 2018

The current logic is here: https://github.com/lampepfl/dotty/blob/34baac4b69ef9bc764c8a6d0d4c8cd458759a5e4/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala#L297-L301

To give some context: we avoid inferring union types for the same reason we avoid inferring singleton types, because sometimes they're "too precise". E.g.:

scala> List(1, "")
val res0: List[Any] = List(1, )

scala> List(if (true) 1 else "")
val res1: List[Any] = List(1)

The problem is that we do not distinguish between an inferred union type and a union type actually written down by the user:

scala> val x: Int | String = 1
val x: Int | String = 1

scala> List(x)                                                                                                                                                                                                                               
val res2: List[Any] = List(1)

This is tricky to fix since we'd need to have a way to distinguish | written down by the user from | made up by the compiler (maybe using an annotation?).

(If you're looking for issues related to type inference, I've left a sketch of how to fix things in #4742, but it's not super easy either)

@abeln
Copy link
Contributor

abeln commented Jul 31, 2018

Thanks! I'll take a look at #4742

@nicolasstucki
Copy link
Contributor

I fell into a similar case with quoted.Expr

class Expr[+T]
def foo(): Char | String = bar/*[Char | String]*/(impl())
def bar[T](expr: Expr[T]): T = ???
def impl(): Expr[Char | String] = ???

Which fails with

8 |  def foo(): Char | String = bar/*[Char | String]*/(impl())
  |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |                             Found:    Any
  |                             Required: Char | String

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 15, 2019
Currently type inference for `Expr` of union types is not precise enough as reported in scala#4867.
This implies that currently we cannot have macros of the form

```scala
inline def foo(x: T) <: X | Y = ${ ... }
```

but we can always default back to the less informative alternative

```scala
inline def foo(x: T) <: Any = ${ ... }
```

Both form will refine the type to the type of the expression returned by the macro.
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 15, 2019
Currently type inference for `Expr` of union types is not precise enough as reported in scala#4867.
This implies that currently we cannot have macros of the form

```scala
inline def foo(x: T) <: X | Y = ${ ... }
```

but we can always default back to the less informative alternative

```scala
inline def foo(x: T) <: Any = ${ ... }
```

Both form will refine the type to the type of the expression returned by the macro.
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 15, 2019
Currently type inference for `Expr` of union types is not precise enough as reported in scala#4867.
This implies that currently we cannot have macros of the form

```scala
inline def foo(x: T) <: X | Y = ${ ... }
```

but we can always default back to the less informative alternative

```scala
inline def foo(x: T) <: Any = ${ ... }
```

Both form will refine the type to the type of the expression returned by the macro.
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 15, 2019
Currently type inference for `Expr` of union types is not precise enough as reported in scala#4867.
This implies that currently we cannot have macros of the form

```scala
inline def foo(x: T) <: X | Y = ${ ... }
```

but we can always default back to the less informative alternative

```scala
inline def foo(x: T) <: Any = ${ ... }
```

Both form will refine the type to the type of the expression returned by the macro.
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 20, 2019
Currently type inference for `Expr` of union types is not precise enough as reported in scala#4867.
This implies that currently we cannot have macros of the form

```scala
inline def foo(x: T) <: X | Y = ${ ... }
```

but we can always default back to the less informative alternative

```scala
inline def foo(x: T) <: Any = ${ ... }
```

Both form will refine the type to the type of the expression returned by the macro.
@Baccata
Copy link

Baccata commented Nov 21, 2019

I'm encountering this issue as well : https://scastie.scala-lang.org/GW8xTsFARp22lzpTI7nb5Q

landerlo added a commit to landerlo/dotty that referenced this issue Dec 20, 2019
Improves the inference of union types by preserving the
union when there is a type ascription with an union.

If the term has not been explicity ascribed with an union then
the existing semantics of joining the orType is maintained.

To determine whether an explicit union ascription exists, a lexical
check on the untyped tree is performed.
landerlo added a commit to landerlo/dotty that referenced this issue Dec 20, 2019
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Dec 20, 2019
@landerlo
Copy link

landerlo commented Dec 20, 2019

I have a PR for this union inference issue: #7829

landerlo added a commit to landerlo/dotty that referenced this issue Dec 28, 2019
Improves the inference of union types by preserving the
union when there is a type ascription with an union.

If the term has not been explicity ascribed with an union then
the existing semantics of joining the orType is maintained.

To determine whether an explicit union ascription exists, a lexical
check on the untyped tree is performed.
landerlo added a commit to landerlo/dotty that referenced this issue Dec 28, 2019
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Dec 28, 2019
landerlo added a commit to landerlo/dotty that referenced this issue Dec 28, 2019
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Dec 28, 2019
landerlo added a commit to landerlo/dotty that referenced this issue Dec 28, 2019
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Dec 28, 2019
landerlo added a commit to landerlo/dotty that referenced this issue Jan 1, 2020
Improves the inference of union types by preserving the
union when there is a type ascription with an union.

If the term has not been explicity ascribed with an union then
the existing semantics of joining the orType is maintained.

To determine whether an explicit union ascription exists, a lexical
check on the untyped tree is performed.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 1, 2020
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 1, 2020
landerlo added a commit to landerlo/dotty that referenced this issue Jan 11, 2020
Improves the inference of union types by preserving the
union when there is a type ascription with an union.

If the term has not been explicity ascribed with an union then
the existing semantics of joining the orType is maintained.

To determine whether an explicit union ascription exists, a lexical
check on the untyped tree is performed.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 11, 2020
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 11, 2020
landerlo added a commit to landerlo/dotty that referenced this issue Jan 15, 2020
Improves the inference of union types by preserving the
union when there is a type ascription with an union.

If the term has not been explicity ascribed with an union then
the existing semantics of joining the orType is maintained.

To determine whether an explicit union ascription exists, a lexical
check on the untyped tree is performed.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 15, 2020
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 15, 2020
landerlo added a commit to landerlo/dotty that referenced this issue Jan 16, 2020
Improves the inference of union types by preserving the
union when there is a type ascription with an union.

If the term has not been explicity ascribed with an union then
the existing semantics of joining the orType is maintained.

To determine whether an explicit union ascription exists, a lexical
check on the untyped tree is performed.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 16, 2020
Splits union.scala test from negative to negative and positive tests,
exhibiting the preservation of the union when the union has been
explicitly ascribed as per fix scala#4867.
landerlo added a commit to landerlo/dotty that referenced this issue Jan 16, 2020
@odersky
Copy link
Contributor

odersky commented Mar 8, 2020

This works by now.

@odersky odersky closed this as completed Mar 8, 2020
odersky added a commit to dotty-staging/dotty that referenced this issue Mar 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants