-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Better resolution of implicits in patterns #13807
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1344,7 +1344,31 @@ trait Applications extends Compatibility { | |
unapplyArgType | ||
} | ||
val dummyArg = dummyTreeOfType(ownType) | ||
val unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil))) | ||
var unapplyApp: tpd.Tree = null | ||
val unapplyAppTpe = { | ||
val explicitCall = Apply(unapplyFn, dummyArg :: Nil) | ||
tryEither { | ||
unapplyApp = typedExpr(untpd.TypedSplice(explicitCall)) | ||
unapplyApp.tpe | ||
} { (_, _) => | ||
// Typing explicitCall may fail if there are implicits that can only | ||
// be resolved with tighter bounds on the output types | ||
// (e.g. def unapply[T](x: String)(using Foo[T]): Option[T]) | ||
// If the call above fails to type, we try again here but with wildcards | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having read through https://contributors.scala-lang.org/t/better-implicit-resolution-in-pattern-matches/5308 I sort of understand the usecase, but I'm not sure it's worth it given this kind of weird business with wildcards needed in the implementation. It seems that pattern typing is fundamentally top-down and this code attempts to work around that in some specific cases, but I can't really tell what the implications of that change is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for follow up. I agree that this implementation is not ideal, but I think I would disagree that pattern typing ought to be fundamentally top-down. I think it is right now, but I don't see any good reason for it. I almost filed the feature request as a bug report because it's not really clear to me whether the current behavior is the spec. If pattern matching were specified as syntactic sugar over a series of called to unapply/flatMap, etc., I think we might find that the behavior specified in the "feature request" would fall out naturally, and this PR would be more of a (possibly hacky) bug fix. I would like to fix the possibly hacky part, but I'll need a little consultation on what would seem less hacky and I suppose agreement on whether the syntactic sugar view of pattern matching is correct. Maybe it's worth starting a separate thread on what that view might be? Or is there already a spec for the bespoke type inference currently implemented beyond the implementation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Scala 2 spec contains this section: https://www.scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#pattern-matching-expressions but I honestly have no idea how closely it matches either the Scala 2 or 3 implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That spec doesn't say anything about how to type a pattern, just how to type a pattern in the context of type variables (IIUC). And if anything, it sounds a lot like the implementation in this PR, except with undefined for type parameters instead of wildcards for implicits?
That said, any view of pattern matching as syntactic sugar for a series of calls to |
||
// filled in for any implicits. Once we have recursively typed unapplyPatterns below, | ||
// type variables may have tighter bounds and we can try again. | ||
unapplyApp = null | ||
var currType = mt.resultType | ||
var callWithImplicits = explicitCall | ||
while (currType.isImplicitMethod) { | ||
val currMtType = currType.asInstanceOf[MethodType] | ||
val wildcards = List.fill(currMtType.paramInfos.length)(dummyTreeOfType(WildcardType)) | ||
callWithImplicits = Apply(callWithImplicits, wildcards) | ||
currType = currMtType.resultType | ||
} | ||
typedExpr(untpd.TypedSplice(callWithImplicits)).tpe | ||
} | ||
} | ||
def unapplyImplicits(unapp: Tree): List[Tree] = { | ||
val res = List.newBuilder[Tree] | ||
def loop(unapp: Tree): Unit = unapp match { | ||
|
@@ -1359,8 +1383,8 @@ trait Applications extends Compatibility { | |
res.result() | ||
} | ||
|
||
var argTypes = unapplyArgs(unapplyApp.tpe, unapplyFn, args, tree.srcPos) | ||
for (argType <- argTypes) assert(!isBounds(argType), unapplyApp.tpe.show) | ||
var argTypes = unapplyArgs(unapplyAppTpe, unapplyFn, args, tree.srcPos) | ||
for (argType <- argTypes) assert(!isBounds(argType), unapplyAppTpe.show) | ||
val bunchedArgs = argTypes match { | ||
case argType :: Nil => | ||
if (args.lengthCompare(1) > 0 && Feature.autoTuplingEnabled && defn.isTupleNType(argType)) untpd.Tuple(args) :: Nil | ||
|
@@ -1373,6 +1397,7 @@ trait Applications extends Compatibility { | |
List.fill(argTypes.length - args.length)(WildcardType) | ||
} | ||
val unapplyPatterns = bunchedArgs.lazyZip(argTypes) map (typed(_, _)) | ||
if (unapplyApp == null) unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-infer types after type variables might have been further constrained. |
||
val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType) | ||
unapp.println(s"unapply patterns = $unapplyPatterns") | ||
if ((ownType eq selType) || ownType.isError) result | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -834,6 +834,9 @@ class Typer extends Namer | |
val expr1 = | ||
if isWildcard then tree.expr.withType(underlyingTreeTpe.tpe) | ||
else typed(tree.expr, underlyingTreeTpe.tpe.widenSkolem) | ||
if (ctx.mode.is(Mode.Pattern)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There must be something smarter here. Currently, |
||
pt <:< underlyingTreeTpe.tpe | ||
} | ||
assignType(cpy.Typed(tree)(expr1, tpt), underlyingTreeTpe) | ||
.withNotNullInfo(expr1.notNullInfo) | ||
} | ||
|
@@ -2019,7 +2022,7 @@ class Typer extends Namer | |
} | ||
|
||
def typedBind(tree: untpd.Bind, pt: Type)(using Context): Tree = { | ||
if !isFullyDefined(pt, ForceDegree.all) then | ||
if !(ctx.mode.is(Mode.Pattern) || isFullyDefined(pt, ForceDegree.all)) then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needed to pass f242-2.scala |
||
return errorTree(tree, i"expected type of $tree is not fully defined") | ||
val body1 = typed(tree.body, pt) | ||
body1 match { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,3 @@ | |
5 | case '{ StringContext(${Varargs(parts)}*) } => // error | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| no implicit argument of type scala.quoted.Quotes was found | ||
-- [E006] Not Found Error: tests/neg-macros/i6436.scala:6:34 ----------------------------------------------------------- | ||
6 | val ps: Seq[Expr[String]] = parts // error | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is an improvement? |
||
| ^^^^^ | ||
| Not found: parts | ||
|
||
longer explanation available when compiling with `-explain` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
-- Error: tests/neg/f242.scala:23:17 ----------------------------------------------------------------------------------- | ||
23 | case Foo.Type(NotAnInstance(_)) => ??? // error: no implicit argument of type Test.Foo[T] was found | ||
| ^ | ||
| no implicit argument of type Test.Foo[T] was found for an implicit parameter of method unapply in object Type | ||
| | ||
| where: T is a type variable with constraint <: Test.NotAnInstance |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
object Test { | ||
trait Foo[Self] | ||
object Foo { | ||
class Type private(t: Any) | ||
|
||
object Type { | ||
def apply[T: Foo](t: T) = new Type(t) | ||
|
||
def unapply[T: Foo](t: Type): Option[T] = ??? | ||
} | ||
} | ||
|
||
case class Instance1(a: String) | ||
given Foo[Instance1]() | ||
|
||
case class Instance2(b: Int) | ||
given Foo[Instance2]() | ||
|
||
case class NotAnInstance(b: Double) | ||
|
||
Foo.Type(Instance1("5")) match { | ||
case Foo.Type(Instance1(_: String)) => ??? | ||
case Foo.Type(NotAnInstance(_)) => ??? // error: no implicit argument of type Test.Foo[T] was found | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
object Test { | ||
object Unapply { | ||
def unapply[T1, T2, T3](x: Int)(using T1)(using T2, T3): Option[(T1, T2, T3)] = ??? | ||
} | ||
given Int = 6 | ||
given String = "s" | ||
given Boolean = false | ||
|
||
5 match { | ||
case Unapply(f: Int, _: String, _: Boolean) if f == 5 => ??? | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
object Test { | ||
object Unapply { | ||
def unapply[T](x: Int)(using T): Option[T] = ??? | ||
} | ||
trait Foo | ||
object Impl extends Foo | ||
|
||
implicit val x: Foo = Impl | ||
implicit val distractor: Int = 5 | ||
|
||
5 match { | ||
case Unapply(Impl: Foo) => ??? | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
object Test { | ||
object Unapply { | ||
def unapply(x: Int): Option[Any] = ??? | ||
} | ||
|
||
5 match { | ||
case Unapply(f: Int) if f == 5 => ??? | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
object Test { | ||
trait Foo[Self] | ||
object Foo { | ||
class Type private(t: Any) | ||
|
||
object Type { | ||
def apply[T: Foo](t: T) = new Type(t) | ||
|
||
def unapply[T: Foo](t: Type): Option[T] = ??? | ||
} | ||
} | ||
|
||
case class Instance1(a: String) | ||
given Foo[Instance1]() | ||
|
||
case class Instance2(b: Int) | ||
given Foo[Instance2]() | ||
|
||
Foo.Type(Instance1("5")) match { | ||
case Foo.Type(Instance1(_: String)) => ??? | ||
case Foo.Type(_: Instance1) => ??? | ||
case Foo.Type(Instance2(_: Int)) => ??? | ||
case Foo.Type(_: Instance2) => ??? | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the core of the change.