@@ -7,7 +7,7 @@ import Flags.*
7
7
import Names .*
8
8
import StdNames .* , NameOps .*
9
9
import NullOpsDecorator .*
10
- import NameKinds .SkolemName
10
+ import NameKinds .{ SkolemName , WildcardParamName }
11
11
import Scopes .*
12
12
import Constants .*
13
13
import Contexts .*
@@ -30,6 +30,8 @@ import Hashable.*
30
30
import Uniques .*
31
31
import collection .mutable
32
32
import config .Config
33
+ import config .Feature .sourceVersion
34
+ import config .SourceVersion
33
35
import annotation .{tailrec , constructorOnly }
34
36
import scala .util .hashing .{ MurmurHash3 => hashing }
35
37
import config .Printers .{core , typr , matchTypes }
@@ -5036,7 +5038,7 @@ object Types extends TypeUtils {
5036
5038
trace(i " reduce match type $this $hashCode" , matchTypes, show = true )(inMode(Mode .Type ) {
5037
5039
def matchCases (cmp : TrackingTypeComparer ): Type =
5038
5040
val saved = ctx.typerState.snapshot()
5039
- try cmp.matchCases(scrutinee.normalized, cases)
5041
+ try cmp.matchCases(scrutinee.normalized, cases.map( MatchTypeCaseSpec .analyze(_)) )
5040
5042
catch case ex : Throwable =>
5041
5043
handleRecursive(" reduce type " , i " $scrutinee match ... " , ex)
5042
5044
finally
@@ -5088,6 +5090,190 @@ object Types extends TypeUtils {
5088
5090
case _ => None
5089
5091
}
5090
5092
5093
+ enum MatchTypeCasePattern :
5094
+ case Capture (num : Int , isWildcard : Boolean )
5095
+ case TypeTest (tpe : Type )
5096
+ case BaseTypeTest (classType : TypeRef , argPatterns : List [MatchTypeCasePattern ], needsConcreteScrut : Boolean )
5097
+ case CompileTimeS (argPattern : MatchTypeCasePattern )
5098
+ case AbstractTypeConstructor (tycon : Type , argPatterns : List [MatchTypeCasePattern ])
5099
+ case TypeMemberExtractor (typeMemberName : TypeName , capture : Capture )
5100
+
5101
+ def isTypeTest : Boolean =
5102
+ this .isInstanceOf [TypeTest ]
5103
+
5104
+ def needsConcreteScrutInVariantPos : Boolean = this match
5105
+ case Capture (_, isWildcard) => ! isWildcard
5106
+ case TypeTest (_) => false
5107
+ case _ => true
5108
+ end MatchTypeCasePattern
5109
+
5110
+ enum MatchTypeCaseSpec :
5111
+ case SubTypeTest (origMatchCase : Type , pattern : Type , body : Type )
5112
+ case SpeccedPatMat (origMatchCase : HKTypeLambda , captureCount : Int , pattern : MatchTypeCasePattern , body : Type )
5113
+ case LegacyPatMat (origMatchCase : HKTypeLambda )
5114
+ case MissingCaptures (origMatchCase : HKTypeLambda , missing : collection.BitSet )
5115
+
5116
+ def origMatchCase : Type
5117
+ end MatchTypeCaseSpec
5118
+
5119
+ object MatchTypeCaseSpec :
5120
+ def analyze (cas : Type )(using Context ): MatchTypeCaseSpec =
5121
+ cas match
5122
+ case cas : HKTypeLambda if ! sourceVersion.isAtLeast(SourceVersion .`3.4`) =>
5123
+ // Always apply the legacy algorithm under -source:3.3 and below
5124
+ LegacyPatMat (cas)
5125
+ case cas : HKTypeLambda =>
5126
+ val defn .MatchCase (pat, body) = cas.resultType: @ unchecked
5127
+ val missing = checkCapturesPresent(cas, pat)
5128
+ if ! missing.isEmpty then
5129
+ MissingCaptures (cas, missing)
5130
+ else
5131
+ val specPattern = tryConvertToSpecPattern(cas, pat)
5132
+ if specPattern != null then
5133
+ SpeccedPatMat (cas, cas.paramNames.size, specPattern, body)
5134
+ else
5135
+ LegacyPatMat (cas)
5136
+ case _ =>
5137
+ val defn .MatchCase (pat, body) = cas : @ unchecked
5138
+ SubTypeTest (cas, pat, body)
5139
+ end analyze
5140
+
5141
+ /** Checks that all the captures of the case are present in the case.
5142
+ *
5143
+ * Sometimes, because of earlier substitutions of an abstract type constructor,
5144
+ * we can end up with patterns that do not mention all their captures anymore.
5145
+ * This can happen even when the body still refers to these missing captures.
5146
+ * In that case, we must always consider the case to be unmatchable, i.e., to
5147
+ * become `Stuck`.
5148
+ *
5149
+ * See pos/i12127.scala for an example.
5150
+ */
5151
+ def checkCapturesPresent (cas : HKTypeLambda , pat : Type )(using Context ): collection.BitSet =
5152
+ val captureCount = cas.paramNames.size
5153
+ val missing = new mutable.BitSet (captureCount)
5154
+ missing ++= (0 until captureCount)
5155
+ new CheckCapturesPresent (cas).apply(missing, pat)
5156
+
5157
+ private class CheckCapturesPresent (cas : HKTypeLambda )(using Context ) extends TypeAccumulator [mutable.BitSet ]:
5158
+ def apply (missing : mutable.BitSet , tp : Type ): mutable.BitSet = tp match
5159
+ case TypeParamRef (binder, num) if binder eq cas =>
5160
+ missing -= num
5161
+ case _ =>
5162
+ foldOver(missing, tp)
5163
+ end CheckCapturesPresent
5164
+
5165
+ /** Tries to convert a match type case pattern in HKTypeLambda form into a spec'ed `MatchTypeCasePattern`.
5166
+ *
5167
+ * This method recovers the structure of *legal patterns* as defined in SIP-56
5168
+ * from the unstructured `HKTypeLambda` coming from the typer.
5169
+ *
5170
+ * It must adhere to the specification of legal patterns defined at
5171
+ * https://docs.scala-lang.org/sips/match-types-spec.html#legal-patterns
5172
+ *
5173
+ * Returns `null` if the pattern in `caseLambda` is a not a legal pattern.
5174
+ */
5175
+ private def tryConvertToSpecPattern (caseLambda : HKTypeLambda , pat : Type )(using Context ): MatchTypeCasePattern | Null =
5176
+ var typeParamRefsAccountedFor : Int = 0
5177
+
5178
+ def rec (pat : Type , variance : Int ): MatchTypeCasePattern | Null =
5179
+ pat match
5180
+ case pat @ TypeParamRef (binder, num) if binder eq caseLambda =>
5181
+ typeParamRefsAccountedFor += 1
5182
+ MatchTypeCasePattern .Capture (num, isWildcard = pat.paramName.is(WildcardParamName ))
5183
+
5184
+ case pat @ AppliedType (tycon : TypeRef , args) if variance == 1 =>
5185
+ val tyconSym = tycon.symbol
5186
+ if tyconSym.isClass then
5187
+ if tyconSym.name.startsWith(" Tuple" ) && defn.isTupleNType(pat) then
5188
+ rec(pat.toNestedPairs, variance)
5189
+ else
5190
+ recArgPatterns(pat) { argPatterns =>
5191
+ val needsConcreteScrut = argPatterns.zip(tycon.typeParams).exists {
5192
+ (argPattern, tparam) => tparam.paramVarianceSign != 0 && argPattern.needsConcreteScrutInVariantPos
5193
+ }
5194
+ MatchTypeCasePattern .BaseTypeTest (tycon, argPatterns, needsConcreteScrut)
5195
+ }
5196
+ else if defn.isCompiletime_S(tyconSym) && args.sizeIs == 1 then
5197
+ val argPattern = rec(args.head, variance)
5198
+ if argPattern == null then
5199
+ null
5200
+ else if argPattern.isTypeTest then
5201
+ MatchTypeCasePattern .TypeTest (pat)
5202
+ else
5203
+ MatchTypeCasePattern .CompileTimeS (argPattern)
5204
+ else
5205
+ tycon.info match
5206
+ case _ : RealTypeBounds =>
5207
+ recAbstractTypeConstructor(pat)
5208
+ case TypeAlias (tl @ HKTypeLambda (onlyParam :: Nil , resType : RefinedType )) =>
5209
+ /* Unlike for eta-expanded classes, the typer does not automatically
5210
+ * dealias poly type aliases to refined types. So we have to give them
5211
+ * a chance here.
5212
+ * We are quite specific about the shape of type aliases that we are willing
5213
+ * to dealias this way, because we must not dealias arbitrary type constructors
5214
+ * that could refine the bounds of the captures; those would amount of
5215
+ * type-test + capture combos, which are out of the specced match types.
5216
+ */
5217
+ rec(pat.superType, variance)
5218
+ case _ =>
5219
+ null
5220
+
5221
+ case pat @ AppliedType (tycon : TypeParamRef , _) if variance == 1 =>
5222
+ recAbstractTypeConstructor(pat)
5223
+
5224
+ case pat @ RefinedType (parent, refinedName : TypeName , TypeAlias (alias @ TypeParamRef (binder, num)))
5225
+ if variance == 1 && (binder eq caseLambda) =>
5226
+ parent.member(refinedName) match
5227
+ case refinedMember : SingleDenotation if refinedMember.exists =>
5228
+ // Check that the bounds of the capture contain the bounds of the inherited member
5229
+ val refinedMemberBounds = refinedMember.info
5230
+ val captureBounds = caseLambda.paramInfos(num)
5231
+ if captureBounds.contains(refinedMemberBounds) then
5232
+ /* In this case, we know that any member we eventually find during reduction
5233
+ * will have bounds that fit in the bounds of the capture. Therefore, no
5234
+ * type-test + capture combo is necessary, and we can apply the specced match types.
5235
+ */
5236
+ val capture = rec(alias, variance = 0 ).asInstanceOf [MatchTypeCasePattern .Capture ]
5237
+ MatchTypeCasePattern .TypeMemberExtractor (refinedName, capture)
5238
+ else
5239
+ // Otherwise, a type-test + capture combo might be necessary, and we are out of spec
5240
+ null
5241
+ case _ =>
5242
+ // If the member does not refine a member of the `parent`, we are out of spec
5243
+ null
5244
+
5245
+ case _ =>
5246
+ MatchTypeCasePattern .TypeTest (pat)
5247
+ end rec
5248
+
5249
+ def recAbstractTypeConstructor (pat : AppliedType ): MatchTypeCasePattern | Null =
5250
+ recArgPatterns(pat) { argPatterns =>
5251
+ MatchTypeCasePattern .AbstractTypeConstructor (pat.tycon, argPatterns)
5252
+ }
5253
+ end recAbstractTypeConstructor
5254
+
5255
+ def recArgPatterns (pat : AppliedType )(whenNotTypeTest : List [MatchTypeCasePattern ] => MatchTypeCasePattern | Null ): MatchTypeCasePattern | Null =
5256
+ val AppliedType (tycon, args) = pat
5257
+ val tparams = tycon.typeParams
5258
+ val argPatterns = args.zip(tparams).map { (arg, tparam) =>
5259
+ rec(arg, tparam.paramVarianceSign)
5260
+ }
5261
+ if argPatterns.exists(_ == null ) then
5262
+ null
5263
+ else
5264
+ val argPatterns1 = argPatterns.asInstanceOf [List [MatchTypeCasePattern ]] // they are not null
5265
+ if argPatterns1.forall(_.isTypeTest) then
5266
+ MatchTypeCasePattern .TypeTest (pat)
5267
+ else
5268
+ whenNotTypeTest(argPatterns1)
5269
+ end recArgPatterns
5270
+
5271
+ val result = rec(pat, variance = 1 )
5272
+ if typeParamRefsAccountedFor == caseLambda.paramNames.size then result
5273
+ else null
5274
+ end tryConvertToSpecPattern
5275
+ end MatchTypeCaseSpec
5276
+
5091
5277
// ------ ClassInfo, Type Bounds --------------------------------------------------
5092
5278
5093
5279
type TypeOrSymbol = Type | Symbol
0 commit comments