Skip to content

Commit

Permalink
Backport "Re-use isConcrete checking in match types for NamedTuple.Fr…
Browse files Browse the repository at this point in the history
…om" to 3.5.2 (#21447)

Backports #20947 to the 3.5.2 branch.

PR submitted by the release tooling.
[skip ci]
  • Loading branch information
WojciechMazur authored Aug 27, 2024
2 parents ef39a28 + 445144d commit 46b3479
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 62 deletions.
10 changes: 1 addition & 9 deletions compiler/src/dotty/tools/dotc/core/CheckRealizable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,7 @@ class CheckRealizable(using Context) {
case _: SingletonType | NoPrefix =>
Realizable
case tp =>
def isConcrete(tp: Type): Boolean = tp.dealias match {
case tp: TypeRef => tp.symbol.isClass
case tp: TypeParamRef => false
case tp: TypeProxy => isConcrete(tp.underlying)
case tp: AndType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
case tp: OrType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
case _ => false
}
if (!isConcrete(tp)) NotConcrete
if !MatchTypes.isConcrete(tp) then NotConcrete
else boundsRealizability(tp).andAlso(memberRealizability(tp))
}

Expand Down
47 changes: 47 additions & 0 deletions compiler/src/dotty/tools/dotc/core/MatchTypes.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dotty.tools
package dotc
package core

import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*

object MatchTypes:

/* Concreteness checking
*
* When following a baseType and reaching a non-wildcard, in-variant-pos type capture,
* we have to make sure that the scrutinee is concrete enough to uniquely determine
* the values of the captures. This comes down to checking that we do not follow any
* upper bound of an abstract type.
*
* See notably neg/wildcard-match.scala for examples of this.
*
* See neg/i13780.scala, neg/i13780-1.scala and neg/i19746.scala for
* ClassCastException reproducers if we disable this check.
*/
def isConcrete(tp: Type)(using Context): Boolean =
val tp1 = tp.normalized

tp1 match
case tp1: TypeRef =>
if tp1.symbol.isClass then true
else
tp1.info match
case info: AliasingBounds => isConcrete(info.alias)
case _ => false
case tp1: AppliedType =>
isConcrete(tp1.tycon) && isConcrete(tp1.superType)
case tp1: HKTypeLambda =>
true
case tp1: TermRef =>
!tp1.symbol.is(Param) && isConcrete(tp1.underlying)
case _: (ParamRef | MatchType) =>
false
case tp1: TypeProxy =>
isConcrete(tp1.underlying)
case tp1: AndOrType =>
isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
case _ =>
false
end isConcrete

end MatchTypes
53 changes: 1 addition & 52 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import reporting.trace
import annotation.constructorOnly
import cc.*
import NameKinds.WildcardParamName
import MatchTypes.isConcrete

/** Provides methods to compare types.
*/
Expand Down Expand Up @@ -3402,58 +3403,6 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {

// See https://docs.scala-lang.org/sips/match-types-spec.html#matching
def matchSpeccedPatMat(spec: MatchTypeCaseSpec.SpeccedPatMat): MatchResult =
/* Concreteness checking
*
* When following a baseType and reaching a non-wildcard, in-variant-pos type capture,
* we have to make sure that the scrutinee is concrete enough to uniquely determine
* the values of the captures. This comes down to checking that we do not follow any
* upper bound of an abstract type.
*
* See notably neg/wildcard-match.scala for examples of this.
*
* See neg/i13780.scala, neg/i13780-1.scala and neg/i19746.scala for
* ClassCastException reproducers if we disable this check.
*/

def isConcrete(tp: Type): Boolean =
val tp1 = tp.normalized

tp1 match
case tp1: TypeRef =>
if tp1.symbol.isClass then true
else
tp1.info match
case info: AliasingBounds => isConcrete(info.alias)
case _ => false
case tp1: AppliedType =>
isConcrete(tp1.tycon) && isConcrete(tp1.superType)
case tp1: HKTypeLambda =>
true
case tp1: TermRef =>
!tp1.symbol.is(Param) && isConcrete(tp1.underlying)
case tp1: TermParamRef =>
false
case tp1: SingletonType =>
isConcrete(tp1.underlying)
case tp1: ExprType =>
isConcrete(tp1.underlying)
case tp1: AnnotatedType =>
isConcrete(tp1.parent)
case tp1: RefinedType =>
isConcrete(tp1.underlying)
case tp1: RecType =>
isConcrete(tp1.underlying)
case tp1: AndOrType =>
isConcrete(tp1.tp1) && isConcrete(tp1.tp2)
case tp1: FlexibleType =>
isConcrete(tp1.hi)
case _ =>
val tp2 = tp1.stripped.stripLazyRef
(tp2 ne tp) && isConcrete(tp2)
end isConcrete

// Actual matching logic

val instances = Array.fill[Type](spec.captureCount)(NoType)
val noInstances = mutable.ListBuffer.empty[(TypeName, TypeBounds)]

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeEval.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ object TypeEval:
expectArgsNum(1)
val arg = tp.args.head
val cls = arg.classSymbol
if cls.is(CaseClass) then
if MatchTypes.isConcrete(arg) && cls.is(CaseClass) then
val fields = cls.caseAccessors
val fieldLabels = fields.map: field =>
ConstantType(Constant(field.name.toString))
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ object Types extends TypeUtils {
* | +- HKTypeLambda
* | +- MatchType
* | +- FlexibleType
* | +- LazyRef
* |
* +- GroundType -+- AndType
* +- OrType
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/i20517.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------
10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
| ^^^^^^^^^^^
| Found: (elem : String)
| Required: NamedTuple.From[(foo : Foo[Any])]
|
| longer explanation available when compiling with `-explain`
17 changes: 17 additions & 0 deletions tests/neg/i20517.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import scala.language.experimental.namedTuples
import NamedTuple.From

case class Foo[+T](elem: T)

trait Base[M[_]]:
def dep(foo: Foo[Any]): M[foo.type]

class SubAny extends Base[From]:
def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error

object Test:
@main def run =
val f: Foo[Int] = Foo(elem = 1)
val b: Base[From] = SubAny()
val nt: (elem: Int) = b.dep(f)
val x: Int = nt.elem // was ClassCastException

0 comments on commit 46b3479

Please sign in to comment.