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