Skip to content

Conversation

@adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Jan 24, 2019

Fix scala/bug#11015
Fix scala/bug#4775
Fix scala/bug#8344

TODO:

  • test failures :-/
  • dig a little deeper into the explanation of why Seq[T] would be seen as compatible to T
    snippet of interest
        // closely mimic monomorphic case of typesCompatible -- check arguments (keeping repeated param types)
        // this ensures that we deal uniformly with repeated params in the mono and poly cases
        val okArgs = isCompatibleArgs(args.map(_.instantiateTypeParams(undetparams, inferredArgs)),
                                      formalTypes(formals.map(_.instantiateTypeParams(undetparams, inferredArgs)), args.length, removeByName = false))

        okArgs && {
          val AdjustedTypeArgs.Undets(okparams, okargs, leftUndet) = adjustTypeArgs(undetparams, tvars, inferredArgs, pt)
          enhanceBounds(okparams, okargs, leftUndet)

The following type checked (choosing the single-param overload):

def foo(xs: Int*): Int = xs.toSeq.head
def foo(x: Int): Int = x
foo(2)

Whereas this was considered ambiguous:

def fooT[T](xs: T*): T = xs.toSeq.head
def fooT[T](x: T): T = x
fooT(2)

Another instance of the mono and poly cases of isApplicableToMethod
being out of whack.

isApplicableToMethod will use formalTypes to eliminate
the repeated param in the formal types at the start,
but we keep the repeated marker in the arguments -- here's a debug log:

/*
inferMethodAlternative all: List(method foo, method foo)
isCompatibleArgs false (List(Int*), List(Int))
isAsSpecific false: (xs: Int*)Int >> (x: Int)Int?
 --> the repeated case is not more specific than the single-arg case because
     you can't apply something of `Int*` to `Int`

isCompatibleArgs true (List(Int), List(Int))
isAsSpecific true: (x: Int)Int >> (xs: Int*)Int?
  --> the single param case is more specific than the repeated param case, because
      you can apply a single argument to the method with the repeated param

isCompatibleArgs true (List(Int), List(Int))
isAsSpecific true: (x: Int)Int >> (xs: Int*)Int?
isCompatibleArgs false (List(Int*), List(Int))
isAsSpecific false: (xs: Int*)Int >> (x: Int)Int?
isCompatibleArgs true (List(Int), List(Int))
isAsSpecific true: (x: Int)Int >> (xs: Int*)Int?
isCompatibleArgs false (List(Int*), List(Int))
isAsSpecific false: (xs: Int*)Int >> (x: Int)Int?
inferMethodAlternative applicable List(method foo, method foo) --> ranked: List(method foo)
 */

The bug in the polymorphic case of isApplicableToMethod
was that adjustTypeArgs would replace the T* by Seq[T],
thus removing the incompatibility in applying a Seq[T] when
a T is expected.

@adriaanm
Copy link
Contributor Author

Or maybe we shouldn’t be instantiating type vars to a non-expression type like a repeated param type?

@adriaanm

This comment has been minimized.

@adriaanm adriaanm force-pushed the overload_poly_repeated branch 2 times, most recently from 033039b to 8adcadb Compare January 24, 2019 15:33
@adriaanm
Copy link
Contributor Author

Welcome to Scala 2.13.0-8adcadb (Java HotSpot(TM) 64-Bit Server VM, Java 11).
Type in expressions for evaluation. Or try :help.

scala> java.util.List.of("")
res0: java.util.List[String] = []

@adriaanm adriaanm requested a review from retronym January 24, 2019 15:37
@adriaanm
Copy link
Contributor Author

Humm, lots of test failures. I'm deferring this to 2.13.1 -- there's no need to get this into 2.13.0

@adriaanm adriaanm modified the milestones: 2.13.0-RC1, 2.13.1 Jan 24, 2019
@adriaanm
Copy link
Contributor Author

I forgot this is affecting specs, moving back to RC1.

@som-snytt
Copy link
Contributor

som-snytt commented Jan 24, 2019

Abstractly, this is because of auto-tupling?

scala/bug#4775
scala/bug#5414

I remember Lukas mentions the implementation on one of the old tickets, maybe not these.

(Object) vs (String*):

scala/bug#8344

@adriaanm adriaanm force-pushed the overload_poly_repeated branch 2 times, most recently from 1c235d6 to fd9dcb2 Compare January 25, 2019 13:29
@adriaanm

This comment has been minimized.

@adriaanm
Copy link
Contributor Author

Thanks! Auto-tupling was also hiding the second class T*, pretending like we can make values of that. Added a few bugs to the fixlist

@adriaanm adriaanm force-pushed the overload_poly_repeated branch from fd9dcb2 to 4cfeab8 Compare January 25, 2019 14:55
@adriaanm
Copy link
Contributor Author

🐰🕳

so, T* aka TypeRef(_, RepeatedParamClass, T) has Seq[T] as a base class, which defines toSeq: this.type, which means xs.toSeq: T* when xs: T*, but now this PR closes the hole that pretended T* was a first-class type in method applications'

i see no other way out but to adapt an expr: T* to expr: Seq[T], unless the expected type is T'*

@som-snytt
Copy link
Contributor

som-snytt commented Jan 25, 2019

To coin a phrase, when I was learning scala the handling of T* was obscure to me on a conceptual level. TIL bunnies live in the man hole cover that leads to the sewer. [Edited.]

scala/bug#4728
scala/bug#4728 (comment)

@adriaanm
Copy link
Contributor Author

🤯scala/bug#3866

@adriaanm
Copy link
Contributor Author

Urban dictionary doesn't help me this time with "to coin a phrase". Mint Might I ask you to elaborate?

@adriaanm adriaanm force-pushed the overload_poly_repeated branch from 4cfeab8 to 41cd12e Compare January 25, 2019 16:39
... through drastic means: T* now only conforms (isCompatible) to
other repeated parameter types. This breaks case classes with
repeated param fields unless we add a case to adapt....

The following type checked (choosing the single-param overload):
```
def foo(xs: Int*): Int = xs.toSeq.head
def foo(x: Int): Int = x
foo(2)
```

Whereas this was considered ambiguous:
```
def fooT[T](xs: T*): T = xs.toSeq.head
def fooT[T](x: T): T = x
fooT(2)
```

Another instance of the mono and poly cases of isApplicableToMethod
being out of whack.

`isApplicableToMethod` will use formalTypes to eliminate
the repeated param in the formal types at the start,
but we keep the repeated marker in the arguments -- here's a debug log:
/*
inferMethodAlternative all: List(method foo, method foo)
isCompatibleArgs false (List(Int*), List(Int))
isAsSpecific false: (xs: Int*)Int >> (x: Int)Int?
 --> the repeated case is not more specific than the single-arg case because
     you can't apply something of `Int*` to `Int`

isCompatibleArgs true (List(Int), List(Int))
isAsSpecific true: (x: Int)Int >> (xs: Int*)Int?
  --> the single param case is more specific than the repeated param case, because
      you can apply a single argument to the method with the repeated param

isCompatibleArgs true (List(Int), List(Int))
isAsSpecific true: (x: Int)Int >> (xs: Int*)Int?
isCompatibleArgs false (List(Int*), List(Int))
isAsSpecific false: (xs: Int*)Int >> (x: Int)Int?
isCompatibleArgs true (List(Int), List(Int))
isAsSpecific true: (x: Int)Int >> (xs: Int*)Int?
isCompatibleArgs false (List(Int*), List(Int))
isAsSpecific false: (xs: Int*)Int >> (x: Int)Int?
inferMethodAlternative applicable List(method foo, method foo) --> ranked: List(method foo)
 */

The bug in the polymorphic case of `isApplicableToMethod`
was that `adjustTypeArgs` would replace the `T*` by `Seq[T]`,
thus removing the incompatibility in applying a `T*` when
a `T` is expected. Sadly, the bounds check also didn't catch it,
since `T* <:< Any`...

There was a similar issue in that we would tuple a T* with other args
while trying to deal with arity mismatch. Also exclude T* there.
@adriaanm adriaanm force-pushed the overload_poly_repeated branch from 41cd12e to 1a2cc1b Compare January 28, 2019 11:30
@adriaanm
Copy link
Contributor Author

🎉

@som-snytt
Copy link
Contributor

Now I'll have to go back and change all my stack overflow answers.

If you're a bad mono case you should stay home and recover.

@adriaanm
Copy link
Contributor Author

This one definitely had me stereo-the-window.

- A parameterized method $m$ of type `($p_1:T_1, \ldots , p_n:T_n$)$U$` is
_as specific as_ some other member $m'$ of type $S$ if $m'$ is [applicable](#function-applications)
to arguments `($p_1 , \ldots , p_n$)` of types $T_1 , \ldots , T_n$.
to arguments `($p_1 , \ldots , p_n$)` of types $T_1 , \ldots , Tlast$;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be Tn rather than Tlast ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tlast is the type of the last arg in the m-prime application; it may be taken as T (instead of T*) or just Tn.

def isCompatibleArgs(tps: List[Type], pts: List[Type]) = {
val res = (tps corresponds pts)(isCompatible)
// println(s"isCompatibleArgs $res : $tps <:< $pts")
res
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need an @inline printing[A](msg, a: => A): A thingie with elidable printing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How I learned to stop worrying about performance and have debug output when I need it. Comments! Higher-tech would be nice, but it would also look like regular code, when it isn't really.


val tp1 = methodToExpressionTp(tp)
// can only compare if both types are repeated or neither is (T* is not actually a first-class type, even though it has a BTS and thus participates in subtyping)
(!isRepeatedParamType(tp) || isRepeatedParamType(pt)) && {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll proceed through this if we have a Java and Scala repeated param type. Array[T] <=> Seq[T] adaptations might then come into play below. I haven't thought this through enough to know if this matters, but did you consider more fine grained checking ("are both/neither java repeated?" "are both/neither Scala repeated").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't give it that much thought, but I think it's ok. This is to shield us from comparing the second-class repeated param types to first-class expression types.

If tp is a repeated param type, so must pt be. If we don't check that, we'll go up the BTS of those types, hitting Seq / Array, and then relating those repeated param types to regular expression types. We should really think of repeated param types as abstract types that you can only convert out of with a conversion (that conversion is already in adapt because of the tighter conformance implemented here.)

instantiatePossiblyExpectingUnit(tree, mode, pt)
}
// TODO: we really shouldn't use T* as a first class types (e.g. for repeated case fields), but we can't allow T* to conform to other types (see isCompatible) because that breaks overload resolution
else if (isScalaRepeatedParamType(tree.tpe) && !isScalaRepeatedParamType(pt)) adapt(tree setType(repeatedToSeq(tree.tpe)), mode, pt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks okay on the assumption that the * only appears in the outermost part of tree.tp / pt.

For case classes, where we have the unfortunate case of * appearing at all, this seems to hold true:

scala> class C(val c: String*)
defined class C

scala> new C("")
res0: C = C@558756be

scala> res0.c
   val c: String*

scala> res0.c
res1: Seq[String] = WrappedArray("")

scala> class C(val c: String*) { def foo = Seq(c) }
defined class C

scala> new C("").foo
   def foo: Seq[Seq[String]]

scala> { () => new C("").foo }
res3: () => Seq[Seq[String]] = $$Lambda$1467/1307655632@2ed7978c

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we shouldn't emit fields with type T*, but then we need to adjust other logic to turn those back into varargs where needed. I chose the expedient route :-)

@sjrd sjrd mentioned this pull request Jan 30, 2019
51 tasks
@adriaanm adriaanm merged commit 0a09f43 into scala:2.13.x Feb 4, 2019
@szeiger szeiger mentioned this pull request Feb 5, 2019
SethTisue added a commit to SethTisue/scala that referenced this pull request Nov 23, 2022
the language in question was added by Adriaan in scala#7680
but then accidentally got removed in 2020 by scala#7432

I looked through other spec commits in that date span and
didn't find anything else that went missing
SethTisue added a commit to SethTisue/scala that referenced this pull request Nov 23, 2022
the language in question was added by Adriaan in scala#7680
but then accidentally got removed in 2020 by scala#7432

I looked through other spec commits in that date span and
didn't find anything else that went missing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants