diff --git a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala index e16a950aa32a..00143f05b4fb 100644 --- a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala +++ b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala @@ -138,8 +138,10 @@ object MatchTypeTrace: | ${casesText(cases)}""" def illegalPatternText(scrut: Type, cas: MatchTypeCaseSpec.LegacyPatMat)(using Context): String = + val explanation = + if cas.err == null then "" else s"The pattern contains ${cas.err.explanation}.\n" i"""The match type contains an illegal case: | ${caseText(cas)} - |(this error can be ignored for now with `-source:3.3`)""" + |$explanation(this error can be ignored for now with `-source:3.3`)""" end MatchTypeTrace diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index cb47bd92352e..6d84242648b2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5307,10 +5307,25 @@ object Types extends TypeUtils { case _ => true end MatchTypeCasePattern + enum MatchTypeCaseError: + case Alias(sym: Symbol) + case RefiningBounds(name: TypeName) + case StructuralType(name: TypeName) + case UnaccountedTypeParam(name: TypeName) + + def explanation(using Context) = this match + case Alias(sym) => i"a type alias `${sym.name}`" + case RefiningBounds(name) => i"an abstract type member `$name` with bounds that need verification" + case StructuralType(name) => i"an abstract type member `$name` that does not refine a member in its parent" + case UnaccountedTypeParam(name) => i"an unaccounted type parameter `$name`" + end MatchTypeCaseError + + type MatchTypeCaseResult = MatchTypeCasePattern | MatchTypeCaseError + enum MatchTypeCaseSpec: case SubTypeTest(origMatchCase: Type, pattern: Type, body: Type) case SpeccedPatMat(origMatchCase: HKTypeLambda, captureCount: Int, pattern: MatchTypeCasePattern, body: Type) - case LegacyPatMat(origMatchCase: HKTypeLambda) + case LegacyPatMat(origMatchCase: HKTypeLambda, err: MatchTypeCaseError | Null) case MissingCaptures(origMatchCase: HKTypeLambda, missing: collection.BitSet) def origMatchCase: Type @@ -5321,18 +5336,18 @@ object Types extends TypeUtils { cas match case cas: HKTypeLambda if !sourceVersion.isAtLeast(SourceVersion.`3.4`) => // Always apply the legacy algorithm under -source:3.3 and below - LegacyPatMat(cas) + LegacyPatMat(cas, null) case cas: HKTypeLambda => val defn.MatchCase(pat, body) = cas.resultType: @unchecked val missing = checkCapturesPresent(cas, pat) if !missing.isEmpty then MissingCaptures(cas, missing) else - val specPattern = tryConvertToSpecPattern(cas, pat) - if specPattern != null then - SpeccedPatMat(cas, cas.paramNames.size, specPattern, body) - else - LegacyPatMat(cas) + tryConvertToSpecPattern(cas, pat) match + case specPattern: MatchTypeCasePattern => + SpeccedPatMat(cas, cas.paramNames.size, specPattern, body) + case err: MatchTypeCaseError => + LegacyPatMat(cas, err) case _ => val defn.MatchCase(pat, body) = cas: @unchecked SubTypeTest(cas, pat, body) @@ -5370,15 +5385,15 @@ object Types extends TypeUtils { * It must adhere to the specification of legal patterns defined at * https://docs.scala-lang.org/sips/match-types-spec.html#legal-patterns * - * Returns `null` if the pattern in `caseLambda` is a not a legal pattern. + * Returns a MatchTypeCaseError if the pattern in `caseLambda` is a not a legal pattern. */ - private def tryConvertToSpecPattern(caseLambda: HKTypeLambda, pat: Type)(using Context): MatchTypeCasePattern | Null = - var typeParamRefsAccountedFor: Int = 0 + private def tryConvertToSpecPattern(caseLambda: HKTypeLambda, pat: Type)(using Context): MatchTypeCaseResult = + var typeParamRefsUnaccountedFor = (0 until caseLambda.paramNames.length).to(mutable.BitSet) - def rec(pat: Type, variance: Int): MatchTypeCasePattern | Null = + def rec(pat: Type, variance: Int): MatchTypeCaseResult = pat match case pat @ TypeParamRef(binder, num) if binder eq caseLambda => - typeParamRefsAccountedFor += 1 + typeParamRefsUnaccountedFor -= num MatchTypeCasePattern.Capture(num, isWildcard = pat.paramName.is(WildcardParamName)) case pat @ AppliedType(tycon: TypeRef, args) if variance == 1 => @@ -5394,13 +5409,13 @@ object Types extends TypeUtils { MatchTypeCasePattern.BaseTypeTest(tycon, argPatterns, needsConcreteScrut) } else if defn.isCompiletime_S(tyconSym) && args.sizeIs == 1 then - val argPattern = rec(args.head, variance) - if argPattern == null then - null - else if argPattern.isTypeTest then - MatchTypeCasePattern.TypeTest(pat) - else - MatchTypeCasePattern.CompileTimeS(argPattern) + rec(args.head, variance) match + case err: MatchTypeCaseError => + err + case argPattern: MatchTypeCasePattern => + if argPattern.isTypeTest + then MatchTypeCasePattern.TypeTest(pat) + else MatchTypeCasePattern.CompileTimeS(argPattern) else tycon.info match case _: RealTypeBounds => @@ -5416,7 +5431,7 @@ object Types extends TypeUtils { */ rec(pat.superType, variance) case _ => - null + MatchTypeCaseError.Alias(tyconSym) case pat @ AppliedType(tycon: TypeParamRef, _) if variance == 1 => recAbstractTypeConstructor(pat) @@ -5437,40 +5452,40 @@ object Types extends TypeUtils { MatchTypeCasePattern.TypeMemberExtractor(refinedName, capture) else // Otherwise, a type-test + capture combo might be necessary, and we are out of spec - null + MatchTypeCaseError.RefiningBounds(refinedName) case _ => // If the member does not refine a member of the `parent`, we are out of spec - null + MatchTypeCaseError.StructuralType(refinedName) case _ => MatchTypeCasePattern.TypeTest(pat) end rec - def recAbstractTypeConstructor(pat: AppliedType): MatchTypeCasePattern | Null = + def recAbstractTypeConstructor(pat: AppliedType): MatchTypeCaseResult = recArgPatterns(pat) { argPatterns => MatchTypeCasePattern.AbstractTypeConstructor(pat.tycon, argPatterns) } end recAbstractTypeConstructor - def recArgPatterns(pat: AppliedType)(whenNotTypeTest: List[MatchTypeCasePattern] => MatchTypeCasePattern | Null): MatchTypeCasePattern | Null = + def recArgPatterns(pat: AppliedType)(whenNotTypeTest: List[MatchTypeCasePattern] => MatchTypeCaseResult): MatchTypeCaseResult = val AppliedType(tycon, args) = pat val tparams = tycon.typeParams val argPatterns = args.zip(tparams).map { (arg, tparam) => rec(arg, tparam.paramVarianceSign) } - if argPatterns.exists(_ == null) then - null - else - val argPatterns1 = argPatterns.asInstanceOf[List[MatchTypeCasePattern]] // they are not null + argPatterns.find(_.isInstanceOf[MatchTypeCaseError]).getOrElse: + val argPatterns1 = argPatterns.asInstanceOf[List[MatchTypeCasePattern]] // they are not errors if argPatterns1.forall(_.isTypeTest) then MatchTypeCasePattern.TypeTest(pat) else whenNotTypeTest(argPatterns1) end recArgPatterns - val result = rec(pat, variance = 1) - if typeParamRefsAccountedFor == caseLambda.paramNames.size then result - else null + rec(pat, variance = 1) match + case err: MatchTypeCaseError => err + case ok if typeParamRefsUnaccountedFor.isEmpty => ok + case _ => + MatchTypeCaseError.UnaccountedTypeParam(caseLambda.paramNames(typeParamRefsUnaccountedFor.head)) end tryConvertToSpecPattern end MatchTypeCaseSpec diff --git a/tests/neg/i17121.check b/tests/neg/i17121.check index 59895dd2474a..4a7dd332d8dc 100644 --- a/tests/neg/i17121.check +++ b/tests/neg/i17121.check @@ -3,22 +3,26 @@ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | The match type contains an illegal case: | case Consumer[List[t]] => t + | The pattern contains an unaccounted type parameter `t`. | (this error can be ignored for now with `-source:3.3`) -- [E191] Type Error: tests/neg/i17121.scala:15:17 --------------------------------------------------------------------- 15 | type G2[X] = X match { case Consumer[Consumer[t]] => t } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | The match type contains an illegal case: | case Consumer[Consumer[t]] => t + | The pattern contains an unaccounted type parameter `t`. | (this error can be ignored for now with `-source:3.3`) -- [E191] Type Error: tests/neg/i17121.scala:17:17 --------------------------------------------------------------------- 17 | type G3[X] = X match { case Consumer[Consumer[Consumer[t]]] => t } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | The match type contains an illegal case: | case Consumer[Consumer[Consumer[t]]] => t + | The pattern contains an unaccounted type parameter `t`. | (this error can be ignored for now with `-source:3.3`) -- [E191] Type Error: tests/neg/i17121.scala:19:17 --------------------------------------------------------------------- 19 | type G4[X] = X match { case Consumer[List[Consumer[t]]] => t } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | The match type contains an illegal case: | case Consumer[List[Consumer[t]]] => t + | The pattern contains an unaccounted type parameter `t`. | (this error can be ignored for now with `-source:3.3`) diff --git a/tests/neg/illegal-match-types.check b/tests/neg/illegal-match-types.check index f5f0f2d07c51..36862f3b9b92 100644 --- a/tests/neg/illegal-match-types.check +++ b/tests/neg/illegal-match-types.check @@ -3,6 +3,7 @@ | ^ | The match type contains an illegal case: | case Inv[Cov[t]] => t + | The pattern contains an unaccounted type parameter `t`. | (this error can be ignored for now with `-source:3.3`) 8 | case Inv[Cov[t]] => t -- [E191] Type Error: tests/neg/illegal-match-types.scala:10:26 -------------------------------------------------------- @@ -10,6 +11,7 @@ | ^ | The match type contains an illegal case: | case Contra[Cov[t]] => t + | The pattern contains an unaccounted type parameter `t`. | (this error can be ignored for now with `-source:3.3`) 11 | case Contra[Cov[t]] => t -- [E191] Type Error: tests/neg/illegal-match-types.scala:15:22 -------------------------------------------------------- @@ -17,6 +19,7 @@ | ^ | The match type contains an illegal case: | case t & Seq[Any] => t + | The pattern contains an unaccounted type parameter `t`. | (this error can be ignored for now with `-source:3.3`) 16 | case t & Seq[Any] => t -- [E191] Type Error: tests/neg/illegal-match-types.scala:22:33 -------------------------------------------------------- @@ -24,19 +27,22 @@ | ^ | The match type contains an illegal case: | case IsSeq[t] => t + | The pattern contains a type alias `IsSeq`. | (this error can be ignored for now with `-source:3.3`) 23 | case IsSeq[t] => t -- [E191] Type Error: tests/neg/illegal-match-types.scala:29:34 -------------------------------------------------------- 29 |type TypeMemberExtractorMT[X] = X match // error | ^ - | The match type contains an illegal case: - | case TypeMemberAux[t] => t - | (this error can be ignored for now with `-source:3.3`) + | The match type contains an illegal case: + | case TypeMemberAux[t] => t + | The pattern contains an abstract type member `TypeMember` that does not refine a member in its parent. + | (this error can be ignored for now with `-source:3.3`) 30 | case TypeMemberAux[t] => t -- [E191] Type Error: tests/neg/illegal-match-types.scala:40:35 -------------------------------------------------------- 40 |type TypeMemberExtractorMT2[X] = X match // error | ^ - | The match type contains an illegal case: - | case TypeMemberAux2[t] => t - | (this error can be ignored for now with `-source:3.3`) + | The match type contains an illegal case: + | case TypeMemberAux2[t] => t + | The pattern contains an abstract type member `TypeMember` with bounds that need verification. + | (this error can be ignored for now with `-source:3.3`) 41 | case TypeMemberAux2[t] => t