Skip to content
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

Re-use isConcrete checking in match types for NamedTuple.From #20947

Merged
merged 2 commits into from
Jul 3, 2024
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
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 @@ -3409,58 +3410,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
Loading