Skip to content

Commit e725743

Browse files
authored
Better error diagnostics for illegal match cases (#20905)
2 parents 4828244 + 9e498fa commit e725743

File tree

4 files changed

+65
-38
lines changed

4 files changed

+65
-38
lines changed

compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,10 @@ object MatchTypeTrace:
138138
| ${casesText(cases)}"""
139139

140140
def illegalPatternText(scrut: Type, cas: MatchTypeCaseSpec.LegacyPatMat)(using Context): String =
141+
val explanation =
142+
if cas.err == null then "" else s"The pattern contains ${cas.err.explanation}.\n"
141143
i"""The match type contains an illegal case:
142144
| ${caseText(cas)}
143-
|(this error can be ignored for now with `-source:3.3`)"""
145+
|$explanation(this error can be ignored for now with `-source:3.3`)"""
144146

145147
end MatchTypeTrace

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5307,10 +5307,25 @@ object Types extends TypeUtils {
53075307
case _ => true
53085308
end MatchTypeCasePattern
53095309

5310+
enum MatchTypeCaseError:
5311+
case Alias(sym: Symbol)
5312+
case RefiningBounds(name: TypeName)
5313+
case StructuralType(name: TypeName)
5314+
case UnaccountedTypeParam(name: TypeName)
5315+
5316+
def explanation(using Context) = this match
5317+
case Alias(sym) => i"a type alias `${sym.name}`"
5318+
case RefiningBounds(name) => i"an abstract type member `$name` with bounds that need verification"
5319+
case StructuralType(name) => i"an abstract type member `$name` that does not refine a member in its parent"
5320+
case UnaccountedTypeParam(name) => i"an unaccounted type parameter `$name`"
5321+
end MatchTypeCaseError
5322+
5323+
type MatchTypeCaseResult = MatchTypeCasePattern | MatchTypeCaseError
5324+
53105325
enum MatchTypeCaseSpec:
53115326
case SubTypeTest(origMatchCase: Type, pattern: Type, body: Type)
53125327
case SpeccedPatMat(origMatchCase: HKTypeLambda, captureCount: Int, pattern: MatchTypeCasePattern, body: Type)
5313-
case LegacyPatMat(origMatchCase: HKTypeLambda)
5328+
case LegacyPatMat(origMatchCase: HKTypeLambda, err: MatchTypeCaseError | Null)
53145329
case MissingCaptures(origMatchCase: HKTypeLambda, missing: collection.BitSet)
53155330

53165331
def origMatchCase: Type
@@ -5321,18 +5336,18 @@ object Types extends TypeUtils {
53215336
cas match
53225337
case cas: HKTypeLambda if !sourceVersion.isAtLeast(SourceVersion.`3.4`) =>
53235338
// Always apply the legacy algorithm under -source:3.3 and below
5324-
LegacyPatMat(cas)
5339+
LegacyPatMat(cas, null)
53255340
case cas: HKTypeLambda =>
53265341
val defn.MatchCase(pat, body) = cas.resultType: @unchecked
53275342
val missing = checkCapturesPresent(cas, pat)
53285343
if !missing.isEmpty then
53295344
MissingCaptures(cas, missing)
53305345
else
5331-
val specPattern = tryConvertToSpecPattern(cas, pat)
5332-
if specPattern != null then
5333-
SpeccedPatMat(cas, cas.paramNames.size, specPattern, body)
5334-
else
5335-
LegacyPatMat(cas)
5346+
tryConvertToSpecPattern(cas, pat) match
5347+
case specPattern: MatchTypeCasePattern =>
5348+
SpeccedPatMat(cas, cas.paramNames.size, specPattern, body)
5349+
case err: MatchTypeCaseError =>
5350+
LegacyPatMat(cas, err)
53365351
case _ =>
53375352
val defn.MatchCase(pat, body) = cas: @unchecked
53385353
SubTypeTest(cas, pat, body)
@@ -5370,15 +5385,15 @@ object Types extends TypeUtils {
53705385
* It must adhere to the specification of legal patterns defined at
53715386
* https://docs.scala-lang.org/sips/match-types-spec.html#legal-patterns
53725387
*
5373-
* Returns `null` if the pattern in `caseLambda` is a not a legal pattern.
5388+
* Returns a MatchTypeCaseError if the pattern in `caseLambda` is a not a legal pattern.
53745389
*/
5375-
private def tryConvertToSpecPattern(caseLambda: HKTypeLambda, pat: Type)(using Context): MatchTypeCasePattern | Null =
5376-
var typeParamRefsAccountedFor: Int = 0
5390+
private def tryConvertToSpecPattern(caseLambda: HKTypeLambda, pat: Type)(using Context): MatchTypeCaseResult =
5391+
var typeParamRefsUnaccountedFor = (0 until caseLambda.paramNames.length).to(mutable.BitSet)
53775392

5378-
def rec(pat: Type, variance: Int): MatchTypeCasePattern | Null =
5393+
def rec(pat: Type, variance: Int): MatchTypeCaseResult =
53795394
pat match
53805395
case pat @ TypeParamRef(binder, num) if binder eq caseLambda =>
5381-
typeParamRefsAccountedFor += 1
5396+
typeParamRefsUnaccountedFor -= num
53825397
MatchTypeCasePattern.Capture(num, isWildcard = pat.paramName.is(WildcardParamName))
53835398

53845399
case pat @ AppliedType(tycon: TypeRef, args) if variance == 1 =>
@@ -5394,13 +5409,13 @@ object Types extends TypeUtils {
53945409
MatchTypeCasePattern.BaseTypeTest(tycon, argPatterns, needsConcreteScrut)
53955410
}
53965411
else if defn.isCompiletime_S(tyconSym) && args.sizeIs == 1 then
5397-
val argPattern = rec(args.head, variance)
5398-
if argPattern == null then
5399-
null
5400-
else if argPattern.isTypeTest then
5401-
MatchTypeCasePattern.TypeTest(pat)
5402-
else
5403-
MatchTypeCasePattern.CompileTimeS(argPattern)
5412+
rec(args.head, variance) match
5413+
case err: MatchTypeCaseError =>
5414+
err
5415+
case argPattern: MatchTypeCasePattern =>
5416+
if argPattern.isTypeTest
5417+
then MatchTypeCasePattern.TypeTest(pat)
5418+
else MatchTypeCasePattern.CompileTimeS(argPattern)
54045419
else
54055420
tycon.info match
54065421
case _: RealTypeBounds =>
@@ -5416,7 +5431,7 @@ object Types extends TypeUtils {
54165431
*/
54175432
rec(pat.superType, variance)
54185433
case _ =>
5419-
null
5434+
MatchTypeCaseError.Alias(tyconSym)
54205435

54215436
case pat @ AppliedType(tycon: TypeParamRef, _) if variance == 1 =>
54225437
recAbstractTypeConstructor(pat)
@@ -5437,40 +5452,40 @@ object Types extends TypeUtils {
54375452
MatchTypeCasePattern.TypeMemberExtractor(refinedName, capture)
54385453
else
54395454
// Otherwise, a type-test + capture combo might be necessary, and we are out of spec
5440-
null
5455+
MatchTypeCaseError.RefiningBounds(refinedName)
54415456
case _ =>
54425457
// If the member does not refine a member of the `parent`, we are out of spec
5443-
null
5458+
MatchTypeCaseError.StructuralType(refinedName)
54445459

54455460
case _ =>
54465461
MatchTypeCasePattern.TypeTest(pat)
54475462
end rec
54485463

5449-
def recAbstractTypeConstructor(pat: AppliedType): MatchTypeCasePattern | Null =
5464+
def recAbstractTypeConstructor(pat: AppliedType): MatchTypeCaseResult =
54505465
recArgPatterns(pat) { argPatterns =>
54515466
MatchTypeCasePattern.AbstractTypeConstructor(pat.tycon, argPatterns)
54525467
}
54535468
end recAbstractTypeConstructor
54545469

5455-
def recArgPatterns(pat: AppliedType)(whenNotTypeTest: List[MatchTypeCasePattern] => MatchTypeCasePattern | Null): MatchTypeCasePattern | Null =
5470+
def recArgPatterns(pat: AppliedType)(whenNotTypeTest: List[MatchTypeCasePattern] => MatchTypeCaseResult): MatchTypeCaseResult =
54565471
val AppliedType(tycon, args) = pat
54575472
val tparams = tycon.typeParams
54585473
val argPatterns = args.zip(tparams).map { (arg, tparam) =>
54595474
rec(arg, tparam.paramVarianceSign)
54605475
}
5461-
if argPatterns.exists(_ == null) then
5462-
null
5463-
else
5464-
val argPatterns1 = argPatterns.asInstanceOf[List[MatchTypeCasePattern]] // they are not null
5476+
argPatterns.find(_.isInstanceOf[MatchTypeCaseError]).getOrElse:
5477+
val argPatterns1 = argPatterns.asInstanceOf[List[MatchTypeCasePattern]] // they are not errors
54655478
if argPatterns1.forall(_.isTypeTest) then
54665479
MatchTypeCasePattern.TypeTest(pat)
54675480
else
54685481
whenNotTypeTest(argPatterns1)
54695482
end recArgPatterns
54705483

5471-
val result = rec(pat, variance = 1)
5472-
if typeParamRefsAccountedFor == caseLambda.paramNames.size then result
5473-
else null
5484+
rec(pat, variance = 1) match
5485+
case err: MatchTypeCaseError => err
5486+
case ok if typeParamRefsUnaccountedFor.isEmpty => ok
5487+
case _ =>
5488+
MatchTypeCaseError.UnaccountedTypeParam(caseLambda.paramNames(typeParamRefsUnaccountedFor.head))
54745489
end tryConvertToSpecPattern
54755490
end MatchTypeCaseSpec
54765491

tests/neg/i17121.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,26 @@
33
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44
| The match type contains an illegal case:
55
| case Consumer[List[t]] => t
6+
| The pattern contains an unaccounted type parameter `t`.
67
| (this error can be ignored for now with `-source:3.3`)
78
-- [E191] Type Error: tests/neg/i17121.scala:15:17 ---------------------------------------------------------------------
89
15 | type G2[X] = X match { case Consumer[Consumer[t]] => t } // error
910
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1011
| The match type contains an illegal case:
1112
| case Consumer[Consumer[t]] => t
13+
| The pattern contains an unaccounted type parameter `t`.
1214
| (this error can be ignored for now with `-source:3.3`)
1315
-- [E191] Type Error: tests/neg/i17121.scala:17:17 ---------------------------------------------------------------------
1416
17 | type G3[X] = X match { case Consumer[Consumer[Consumer[t]]] => t } // error
1517
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1618
| The match type contains an illegal case:
1719
| case Consumer[Consumer[Consumer[t]]] => t
20+
| The pattern contains an unaccounted type parameter `t`.
1821
| (this error can be ignored for now with `-source:3.3`)
1922
-- [E191] Type Error: tests/neg/i17121.scala:19:17 ---------------------------------------------------------------------
2023
19 | type G4[X] = X match { case Consumer[List[Consumer[t]]] => t } // error
2124
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2225
| The match type contains an illegal case:
2326
| case Consumer[List[Consumer[t]]] => t
27+
| The pattern contains an unaccounted type parameter `t`.
2428
| (this error can be ignored for now with `-source:3.3`)

tests/neg/illegal-match-types.check

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,46 @@
33
| ^
44
| The match type contains an illegal case:
55
| case Inv[Cov[t]] => t
6+
| The pattern contains an unaccounted type parameter `t`.
67
| (this error can be ignored for now with `-source:3.3`)
78
8 | case Inv[Cov[t]] => t
89
-- [E191] Type Error: tests/neg/illegal-match-types.scala:10:26 --------------------------------------------------------
910
10 |type ContraNesting[X] = X match // error
1011
| ^
1112
| The match type contains an illegal case:
1213
| case Contra[Cov[t]] => t
14+
| The pattern contains an unaccounted type parameter `t`.
1315
| (this error can be ignored for now with `-source:3.3`)
1416
11 | case Contra[Cov[t]] => t
1517
-- [E191] Type Error: tests/neg/illegal-match-types.scala:15:22 --------------------------------------------------------
1618
15 |type AndTypeMT[X] = X match // error
1719
| ^
1820
| The match type contains an illegal case:
1921
| case t & Seq[Any] => t
22+
| The pattern contains an unaccounted type parameter `t`.
2023
| (this error can be ignored for now with `-source:3.3`)
2124
16 | case t & Seq[Any] => t
2225
-- [E191] Type Error: tests/neg/illegal-match-types.scala:22:33 --------------------------------------------------------
2326
22 |type TypeAliasWithBoundMT[X] = X match // error
2427
| ^
2528
| The match type contains an illegal case:
2629
| case IsSeq[t] => t
30+
| The pattern contains a type alias `IsSeq`.
2731
| (this error can be ignored for now with `-source:3.3`)
2832
23 | case IsSeq[t] => t
2933
-- [E191] Type Error: tests/neg/illegal-match-types.scala:29:34 --------------------------------------------------------
3034
29 |type TypeMemberExtractorMT[X] = X match // error
3135
| ^
32-
| The match type contains an illegal case:
33-
| case TypeMemberAux[t] => t
34-
| (this error can be ignored for now with `-source:3.3`)
36+
| The match type contains an illegal case:
37+
| case TypeMemberAux[t] => t
38+
| The pattern contains an abstract type member `TypeMember` that does not refine a member in its parent.
39+
| (this error can be ignored for now with `-source:3.3`)
3540
30 | case TypeMemberAux[t] => t
3641
-- [E191] Type Error: tests/neg/illegal-match-types.scala:40:35 --------------------------------------------------------
3742
40 |type TypeMemberExtractorMT2[X] = X match // error
3843
| ^
39-
| The match type contains an illegal case:
40-
| case TypeMemberAux2[t] => t
41-
| (this error can be ignored for now with `-source:3.3`)
44+
| The match type contains an illegal case:
45+
| case TypeMemberAux2[t] => t
46+
| The pattern contains an abstract type member `TypeMember` with bounds that need verification.
47+
| (this error can be ignored for now with `-source:3.3`)
4248
41 | case TypeMemberAux2[t] => t

0 commit comments

Comments
 (0)