Skip to content

Avoid wildcards when typing default parameters of context function type #12048

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

Merged
merged 3 commits into from
Apr 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,30 @@ class Namer { typer: Typer =>
case _ =>
NoType

/** The expected type for a default argument. This is normally the `defaultParamType`
* with references to internal parameters replaced by wildcards. This replacement
* makes it possible that the default argument can have a more specific type than the
* parameter. For instance, we allow
*
* class C[A](a: A) { def copy[B](x: B = a): C[B] = C(x) }
*
* However, if the default parameter type is a context function type, we
* have to make sure that wildcard types do not leak into the implicitly
* generated closure's result type. Test case is pos/i12019.scala. If there
* would be a leakage with the wildcard approximation, we pick the original
* default parameter type as expected type.
*/
def expectedDefaultArgType =
val originalTp = defaultParamType
val approxTp = wildApprox(originalTp)
approxTp.stripPoly match
case atp @ defn.ContextFunctionType(_, resType, _)
if !defn.isNonRefinedFunction(atp) // in this case `resType` is lying, gives us only the non-dependent upper bound
|| resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) =>
originalTp
case _ =>
approxTp

// println(s"final inherited for $sym: ${inherited.toString}") !!!
// println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}")
// TODO Scala 3.1: only check for inline vals (no final ones)
Expand All @@ -1509,7 +1533,7 @@ class Namer { typer: Typer =>
// expected type but we run it through `wildApprox` to allow default
// parameters like in `def mkList[T](value: T = 1): List[T]`.
val defaultTp = defaultParamType
val pt = inherited.orElse(wildApprox(defaultTp)).orElse(WildcardType).widenExpr
val pt = inherited.orElse(expectedDefaultArgType).orElse(WildcardType).widenExpr
val tp = typedAheadRhs(pt).tpe
if (defaultTp eq pt) && (tp frozen_<:< defaultTp) then
// When possible, widen to the default getter parameter type to permit a
Expand Down
20 changes: 20 additions & 0 deletions tests/neg/i12019.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
object test1:
trait A
type B <: AnyRef
val test1: A ?=> B = null // error
val test2: A ?=> B = A ?=> null // error

import scala.quoted.*

object Eg1 {

// no default arg: ok
def ok (f: (q: Quotes) ?=> q.reflect.Term) = ()

// default the function *reference* to null: compilation error
def ko_1(f: (q: Quotes) ?=> q.reflect.Term = null) = () // error

// default the function *result* to null: compilation error
def ko_2(f: (q: Quotes) ?=> q.reflect.Term = (_: Quotes) ?=> null) = () // error
}

21 changes: 21 additions & 0 deletions tests/pos/i12019.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
trait A:
type X >: Null

def ko1(f: (q: A) ?=> Int => q.X = null) = ()
def ko2(f: (q: A) ?=> Int => q.X = (_: A) ?=> null) = ()
def ko3(f: (q: A) => q.X = (q => null)) = ()


import scala.quoted.*

object Eg2 {

// no default arg: ok
def ok (f: (q: Quotes) ?=> q.reflect.ValDef => q.reflect.Term) = ()

// default the function *reference* to null: crash!
def ko_1(f: (q: Quotes) ?=> q.reflect.ValDef => q.reflect.Term = null) = ()

// default the function *result* to null: crash!
def ko_2(f: (q: Quotes) ?=> q.reflect.ValDef => q.reflect.Term = (_: Quotes) ?=> null) = ()
}