Skip to content

Fix #5077: avoid pattern-bound type for selectors #10672

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 1 commit into from
Dec 10, 2020
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
18 changes: 13 additions & 5 deletions compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,15 @@ object PatternMatcher {
*/
private val initializer = MutableSymbolMap[Tree]()

private def newVar(rhs: Tree, flags: FlagSet): TermSymbol =
private def newVar(rhs: Tree, flags: FlagSet, tpe: Type): TermSymbol =
newSymbol(ctx.owner, PatMatStdBinderName.fresh(), Synthetic | Case | flags,
sanitize(rhs.tpe), coord = rhs.span)
sanitize(tpe), coord = rhs.span)
// TODO: Drop Case once we use everywhere else `isPatmatGenerated`.

/** The plan `let x = rhs in body(x)` where `x` is a fresh variable */
private def letAbstract(rhs: Tree)(body: Symbol => Plan): Plan = {
val vble = newVar(rhs, EmptyFlags)
private def letAbstract(rhs: Tree, tpe: Type = NoType)(body: Symbol => Plan): Plan = {
val declTpe = if tpe.exists then tpe else rhs.tpe
val vble = newVar(rhs, EmptyFlags, declTpe)
initializer(vble) = rhs
LetPlan(vble, body(vble))
}
Expand Down Expand Up @@ -223,6 +224,13 @@ object PatternMatcher {
/** Plan for matching `scrutinee` symbol against `tree` pattern */
private def patternPlan(scrutinee: Symbol, tree: Tree, onSuccess: Plan): Plan = {

extension (tree: Tree) def avoidPatBoundType(): Type =
tree.tpe.widen match
case tref: TypeRef if tref.symbol.isPatternBound =>
defn.AnyType
case _ =>
tree.tpe

/** Plan for matching `selectors` against argument patterns `args` */
def matchArgsPlan(selectors: List[Tree], args: List[Tree], onSuccess: Plan): Plan = {
/* For a case with arguments that have some test on them such as
Expand All @@ -243,7 +251,7 @@ object PatternMatcher {
*/
def matchArgsSelectorsPlan(selectors: List[Tree], syms: List[Symbol]): Plan =
selectors match {
case selector :: selectors1 => letAbstract(selector)(sym => matchArgsSelectorsPlan(selectors1, sym :: syms))
case selector :: selectors1 => letAbstract(selector, selector.avoidPatBoundType())(sym => matchArgsSelectorsPlan(selectors1, sym :: syms))
case Nil => matchArgsPatternPlan(args, syms.reverse)
}
def matchArgsPatternPlan(args: List[Tree], syms: List[Symbol]): Plan =
Expand Down
3 changes: 3 additions & 0 deletions tests/run/i5077.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
A String with length 4
A String with length 4
A String with length 4
33 changes: 33 additions & 0 deletions tests/run/i5077.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
trait Is[A]
case object IsInt extends Is[Int]
case object IsString extends Is[String]
case class C[A](is: Is[A], value: A)

@main
def Test = {
val c_string: C[String] = C(IsString, "name")
val c_any: C[_] = c_string
val any: Any = c_string

// Case 1: no error
// `IsInt.equals` might be overridden to match a value of `C[String]`
c_string match {
case C(IsInt, _) => println(s"An Int") // Can't possibly happen!
case C(IsString, s) => println(s"A String with length ${s.length}")
case _ => println("No match")
}

// Case 2: Should match the second case and print the length of the string
c_any match {
case C(IsInt, i) if i < 10 => println(s"An Int less than 10")
case C(IsString, s) => println(s"A String with length ${s.length}")
case _ => println("No match")
}

// Case 3: Same as above; should match the second case and print the length of the string
any match {
case C(IsInt, i) if i < 10 => println(s"An Int less than 10")
case C(IsString, s) => println(s"A String with length ${s.length}")
case _ => println("No match")
}
}