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] {
172
172
AlreadyDefinedID ,
173
173
CaseClassInInlinedCodeID ,
174
174
OverrideTypeMismatchErrorID ,
175
- OverrideErrorID
175
+ OverrideErrorID ,
176
+ MatchableWarningID
176
177
177
178
def errorNumber = ordinal - 2
178
179
}
Original file line number Diff line number Diff line change @@ -838,6 +838,32 @@ import transform.SymUtils._
838
838
def explain = " "
839
839
}
840
840
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
+
841
867
class SeqWildcardPatternPos ()(using Context )
842
868
extends SyntaxMsg (SeqWildcardPatternPosID ) {
843
869
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 {
1280
1280
def checkMatchable (tp : Type , pos : SrcPos , pattern : Boolean )(using Context ): Unit =
1281
1281
if ! tp.derivesFrom(defn.MatchableClass ) && sourceVersion.isAtLeast(`future-migration`) then
1282
1282
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)
1286
1284
}
1287
1285
1288
1286
trait ReChecking extends Checking {
Original file line number Diff line number Diff line change @@ -158,3 +158,14 @@ end summonAll
158
158
159
159
/** Assertion that an argument is by-name. Used for nullability checking. */
160
160
def 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