File tree Expand file tree Collapse file tree 6 files changed +86
-4
lines changed
compiler/src/dotty/tools/dotc
library/src/scala/compiletime
neg-custom-args/fatal-warnings Expand file tree Collapse file tree 6 files changed +86
-4
lines changed Original file line number Diff line number Diff line change @@ -172,7 +172,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
172172 AlreadyDefinedID ,
173173 CaseClassInInlinedCodeID ,
174174 OverrideTypeMismatchErrorID ,
175- OverrideErrorID
175+ OverrideErrorID ,
176+ MatchableWarningID
176177
177178 def errorNumber = ordinal - 2
178179}
Original file line number Diff line number Diff line change @@ -838,6 +838,32 @@ import transform.SymUtils._
838838 def explain = " "
839839 }
840840
841+ class MatchableWarning (tp : Type , pattern : Boolean )(using Context )
842+ extends TypeMsg (MatchableWarningID ) {
843+ def msg =
844+ val kind = if pattern then " pattern selector" else " value"
845+ em """ ${kind} should be an instance of Matchable,,
846+ |but it has unmatchable type $tp instead """
847+
848+ def explain =
849+ if pattern then
850+ em """ A value of type $tp cannot be the selector of a match expression
851+ |since it is not constrained to be `Matchable`. Matching on unconstrained
852+ |values is disallowed since it can uncover implementation details that
853+ |were intended to be hidden and thereby can violate paramtetricity laws
854+ |for reasoning about programs.
855+ |
856+ |The restriction can be overridden by appending `.asMatchable` to
857+ |the selector value. `asMatchable` needs to be imported from
858+ |scala.compiletime. Example:
859+ |
860+ | import compiletime.asMatchable
861+ | def f[X](x: X) = x.asMatchable match { ... } """
862+ else
863+ em """ The value can be converted to a `Matchable` by appending `.asMatchable`.
864+ |`asMatchable` needs to be imported from scala.compiletime. """
865+ }
866+
841867 class SeqWildcardPatternPos ()(using Context )
842868 extends SyntaxMsg (SeqWildcardPatternPosID ) {
843869 def msg = em """ ${hl(" *" )} can be used only for last argument """
Original file line number Diff line number Diff line change @@ -1280,9 +1280,7 @@ trait Checking {
12801280 def checkMatchable (tp : Type , pos : SrcPos , pattern : Boolean )(using Context ): Unit =
12811281 if ! tp.derivesFrom(defn.MatchableClass ) && sourceVersion.isAtLeast(`future-migration`) then
12821282 val kind = if pattern then " pattern selector" else " value"
1283- report.warning(
1284- em """ ${kind} should be an instance of Matchable,
1285- |but it has unmatchable type $tp instead """ , pos)
1283+ report.warning(MatchableWarning (tp, pattern), pos)
12861284}
12871285
12881286trait ReChecking extends Checking {
Original file line number Diff line number Diff line change @@ -158,3 +158,14 @@ end summonAll
158158
159159/** Assertion that an argument is by-name. Used for nullability checking. */
160160def byName [T ](x : => T ): T = x
161+
162+ /** Casts a value to be `Matchable`. This is needed if the value's type is an unconstrained
163+ * type parameter and the value is the scrutinee of a match expression.
164+ * This is normally disallowed since it violates parametricity and allows
165+ * to uncover implementation details that were intended to be hidden.
166+ * The `asMatchable` escape hatch should be used sparingly. It's usually
167+ * better to constrain the scrutinee type to be `Matchable` in the first place.
168+ */
169+ extension [T ](x : T )
170+ transparent inline def asMatchable : x.type & Matchable = x.asInstanceOf [x.type & Matchable ]
171+
Original file line number Diff line number Diff line change 1+ import language .future
2+ @ main def Test =
3+ type LeafElem [X ] = X match
4+ case String => Char
5+ case Array [t] => LeafElem [t]
6+ case Iterable [t] => LeafElem [t]
7+ case AnyVal => X
8+
9+ def leafElem [X ](x : X ): LeafElem [X ] = x match
10+ case x : String => x.charAt(0 ) // error
11+ case x : Array [t] => leafElem(x(1 )) // error
12+ case x : Iterable [t] => leafElem(x.head) // error
13+ case x : AnyVal => x // error
Original file line number Diff line number Diff line change 1+ import language .future
2+ import compiletime .asMatchable
3+
4+ @ main def Test =
5+ type LeafElem [X ] = X match
6+ case String => Char
7+ case Array [t] => LeafElem [t]
8+ case Iterable [t] => LeafElem [t]
9+ case AnyVal => X
10+
11+ def leafElem [X ](x : X ): LeafElem [X ] = x.asMatchable match
12+ case x : String => x.charAt(0 )
13+ case x : Array [t] => leafElem(x(1 ))
14+ case x : Iterable [t] => leafElem(x.head)
15+ case x : AnyVal => x
16+
17+ def f [X ](x : X ) = x
18+
19+ def leafElem2 [X ](x : X ): LeafElem [X ] = f(x).asMatchable match
20+ case x : String => x.charAt(0 )
21+ case x : Array [t] => leafElem(x(1 ))
22+ case x : Iterable [t] => leafElem(x.head)
23+ case x : AnyVal => x
24+
25+ val x1 : Char = leafElem(" a" )
26+ assert(x1 == 'a' )
27+ val x2 : Char = leafElem(Array (" a" , " b" ))
28+ assert(x2 == 'b' )
29+ val x3 : Char = leafElem(List (Array (" a" , " b" ), Array (" " )))
30+ assert(x3 == 'b' )
31+ val x4 : Int = leafElem(3 )
32+ assert(x4 == 3 )
33+
You can’t perform that action at this time.
0 commit comments