Skip to content

Commit

Permalink
Restrict syntax of typed patterns (#16150)
Browse files Browse the repository at this point in the history
Fix undefined
Fixes undefined
  • Loading branch information
dwijnand authored Nov 17, 2022
2 parents 671b1f5 + 27ba2e1 commit c5181b9
Show file tree
Hide file tree
Showing 14 changed files with 97 additions and 29 deletions.
13 changes: 8 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1277,11 +1277,14 @@ object Types {
* then the top-level union isn't widened. This is needed so that type inference can infer nullable types.
*/
def widenUnion(using Context): Type = widen match
case tp @ OrNull(tp1): OrType =>
// Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions.
val tp1Widen = tp1.widenUnionWithoutNull
if (tp1Widen.isRef(defn.AnyClass)) tp1Widen
else tp.derivedOrType(tp1Widen, defn.NullType)
case tp: OrType => tp match
case OrNull(tp1) =>
// Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions.
val tp1Widen = tp1.widenUnionWithoutNull
if (tp1Widen.isRef(defn.AnyClass)) tp1Widen
else tp.derivedOrType(tp1Widen, defn.NullType)
case _ =>
tp.widenUnionWithoutNull
case tp =>
tp.widenUnionWithoutNull

Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2822,11 +2822,14 @@ object Parsers {
if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1(location) :: patternAlts(location) }
else Nil

/** Pattern1 ::= Pattern2 [Ascription]
/** Pattern1 ::= PatVar Ascription
* | [‘-’] integerLiteral Ascription
* | [‘-’] floatingPointLiteral Ascription
* | Pattern2
*/
def pattern1(location: Location = Location.InPattern): Tree =
val p = pattern2()
if in.isColon then
if (isVarPattern(p) || p.isInstanceOf[Number]) && in.isColon then
in.nextToken()
ascription(p, location)
else p
Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -476,13 +476,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
* (x: T | Null) => x.$asInstanceOf$[x.type & T]
*/
def toNotNullTermRef(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match
case ref @ OrNull(tpnn) : TermRef
case ref: TermRef
if pt != AssignProto && // Ensure it is not the lhs of Assign
ctx.notNullInfos.impliesNotNull(ref) &&
// If a reference is in the context, it is already trackable at the point we add it.
// Hence, we don't use isTracked in the next line, because checking use out of order is enough.
!ref.usedOutOfOrder =>
tree.cast(AndType(ref, tpnn))
ref match
case OrNull(tpnn) => tree.cast(AndType(ref, tpnn))
case _ => tree
case _ =>
tree

Expand Down
5 changes: 4 additions & 1 deletion docs/_docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,10 @@ TypeCaseClauses ::= TypeCaseClause { TypeCaseClause }
TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi]
Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats)
Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe))
Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe))
| [‘-’] integerLiteral ‘:’ RefinedType Typed(pat, tpe)
| [‘-’] floatingPointLiteral ‘:’ RefinedType Typed(pat, tpe)
| Pattern2
Pattern2 ::= [id ‘@’] InfixPattern [‘*’] Bind(name, pat)
InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat)
SimplePattern ::= PatVar Ident(wildcard)
Expand Down
5 changes: 4 additions & 1 deletion docs/_docs/reference/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,10 @@ TypeCaseClauses ::= TypeCaseClause { TypeCaseClause }
TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi]
Pattern ::= Pattern1 { ‘|’ Pattern1 }
Pattern1 ::= Pattern2 [‘:’ RefinedType]
Pattern1 ::= PatVar ‘:’ RefinedType
| [‘-’] integerLiteral ‘:’ RefinedType
| [‘-’] floatingPointLiteral ‘:’ RefinedType
| Pattern2
Pattern2 ::= [id ‘@’] InfixPattern [‘*’]
InfixPattern ::= SimplePattern { id [nl] SimplePattern }
SimplePattern ::= PatVar
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/i10994.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def foo = true match
case (b: Boolean): Boolean => () // error
61 changes: 61 additions & 0 deletions tests/neg/i15893.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
sealed trait NatT
case class Zero() extends NatT
case class Succ[+N <: NatT](n: N) extends NatT

type Mod2[N <: NatT] <: NatT = N match
case Zero => Zero
case Succ[Zero] => Succ[Zero]
case Succ[Succ[predPredN]] => Mod2[predPredN]

def mod2(n: NatT): NatT = n match
case Zero() => Zero()
case Succ(Zero()) => Succ(Zero())
case Succ(Succ(predPredN)) => mod2(predPredN)

inline def inlineMod2(inline n: NatT): NatT = inline n match
case Zero() => Zero()
case Succ(Zero()) => Succ(Zero())
case Succ(Succ(predPredN)) => inlineMod2(predPredN)

transparent inline def transparentInlineMod2(inline n: NatT): NatT = inline n match
case Zero() => Zero()
case Succ(Zero()) => Succ(Zero())
case Succ(Succ(predPredN)) => transparentInlineMod2(predPredN)

def dependentlyTypedMod2[N <: NatT](n: N): Mod2[N] = n match // exhaustivity warning; unexpected
case Zero(): Zero => Zero() // error
case Succ(Zero()): Succ[Zero] => Succ(Zero()) // error
case Succ(Succ(predPredN)): Succ[Succ[_]] => dependentlyTypedMod2(predPredN) // error

inline def inlineDependentlyTypedMod2[N <: NatT](inline n: N): Mod2[N] = inline n match
case Zero(): Zero => Zero() // error
case Succ(Zero()): Succ[Zero] => Succ(Zero()) // error
case Succ(Succ(predPredN)): Succ[Succ[_]] => inlineDependentlyTypedMod2(predPredN) // error

transparent inline def transparentInlineDependentlyTypedMod2[N <: NatT](inline n: N): Mod2[N] = inline n match
case Zero(): Zero => Zero() // error
case Succ(Zero()): Succ[Zero] => Succ(Zero()) // error
case Succ(Succ(predPredN)): Succ[Succ[_]] => transparentInlineDependentlyTypedMod2(predPredN) // error

def foo(n: NatT): NatT = mod2(n) match
case Succ(Zero()) => Zero()
case _ => n

inline def inlineFoo(inline n: NatT): NatT = inline inlineMod2(n) match
case Succ(Zero()) => Zero()
case _ => n

inline def transparentInlineFoo(inline n: NatT): NatT = inline transparentInlineMod2(n) match
case Succ(Zero()) => Zero()
case _ => n

@main def main(): Unit =
println(mod2(Succ(Succ(Succ(Zero()))))) // prints Succ(Zero()), as expected
println(foo(Succ(Succ(Succ(Zero()))))) // prints Zero(), as expected
println(inlineMod2(Succ(Succ(Succ(Zero()))))) // prints Succ(Zero()), as expected
println(inlineFoo(Succ(Succ(Succ(Zero()))))) // prints Succ(Succ(Succ(Zero()))); unexpected
println(transparentInlineMod2(Succ(Succ(Succ(Zero()))))) // prints Succ(Zero()), as expected
println(transparentInlineFoo(Succ(Succ(Succ(Zero()))))) // prints Zero(), as expected
println(dependentlyTypedMod2(Succ(Succ(Succ(Zero()))))) // runtime error; unexpected
// println(inlineDependentlyTypedMod2(Succ(Succ(Succ(Zero()))))) // doesn't compile; unexpected
// println(transparentInlineDependentlyTypedMod2(Succ(Succ(Succ(Zero()))))) // doesn't compile; unexpected
6 changes: 3 additions & 3 deletions tests/neg/t5702-neg-bad-and-wild.check
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
| pattern expected
|
| longer explanation available when compiling with `-explain`
-- [E040] Syntax Error: tests/neg/t5702-neg-bad-and-wild.scala:13:23 ---------------------------------------------------
-- [E040] Syntax Error: tests/neg/t5702-neg-bad-and-wild.scala:13:22 ---------------------------------------------------
13 | case List(1, _*3:) => // error // error
| ^
| an identifier expected, but ')' found
| ^
| ')' expected, but ':' found
-- [E032] Syntax Error: tests/neg/t5702-neg-bad-and-wild.scala:15:18 ---------------------------------------------------
15 | case List(x*, 1) => // error: pattern expected
| ^
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-macros/i11211.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def takeOptionImpl2[T](using Quotes, Type[T]): Unit = '{
def takeOptionImpl[T](o: Expr[Option[T]], default: Expr[T])(using Quotes, Type[T]): Expr[T] = '{
$o match {
case Some(t1) => t1
case None: Option[T] => $default
case None => $default
}
}

Expand Down
2 changes: 0 additions & 2 deletions tests/pos-special/fatal-warnings/i10994.scala

This file was deleted.

7 changes: 1 addition & 6 deletions tests/pos/patmat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object Test {
}

(xs.length, xs) match {
case (0, Nil: List[Int]) => println("1")
case (0, Nil) => println("1")
case (_, Nil) => println("2")
case (0, _) => println("3")
case (x, y) => println("4")
Expand Down Expand Up @@ -46,9 +46,4 @@ object Test {
case Some(s) => println(s)
case None => println("nothing")
}

type IntPair = (Int, Int)
??? match {
case (x, y): IntPair => x * y
}
}
2 changes: 1 addition & 1 deletion tests/semanticdb/expect/ValPattern.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class ValPattern/*<-example::ValPattern#*/ {
val Some/*->scala::Some.*/(number1/*<-example::ValPattern#number1.*/) =
Some/*->scala::Some.*/(1)

val List/*->scala::package.List.*/(Some/*->scala::Some.*/(q1/*<-example::ValPattern#q1.*/), None/*->scala::None.*/: None/*->scala::None.*/.type, None/*->scala::None.*/) = ???/*->scala::Predef.`???`().*/
val List/*->scala::package.List.*/(Some/*->scala::Some.*/(q1/*<-example::ValPattern#q1.*/), None/*->scala::None.*/) = ???/*->scala::Predef.`???`().*/

var (leftVar/*<-example::ValPattern#leftVar().*/, rightVar/*<-example::ValPattern#rightVar().*/) = (1, 2)
var Some/*->scala::Some.*/(number1Var/*<-example::ValPattern#number1Var().*/) =
Expand Down
2 changes: 1 addition & 1 deletion tests/semanticdb/expect/ValPattern.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class ValPattern {
val Some(number1) =
Some(1)

val List(Some(q1), None: None.type, None) = ???
val List(Some(q1), None) = ???

var (leftVar, rightVar) = (1, 2)
var Some(number1Var) =
Expand Down
6 changes: 2 additions & 4 deletions tests/semanticdb/metac.expect
Original file line number Diff line number Diff line change
Expand Up @@ -3493,7 +3493,7 @@ Uri => ValPattern.scala
Text => empty
Language => Scala
Symbols => 22 entries
Occurrences => 46 entries
Occurrences => 44 entries
Synthetics => 11 entries

Symbols:
Expand Down Expand Up @@ -3532,9 +3532,7 @@ Occurrences:
[8:11..8:15): Some -> scala/Some.
[8:16..8:18): q1 <- example/ValPattern#q1.
[8:21..8:25): None -> scala/None.
[8:27..8:31): None -> scala/None.
[8:38..8:42): None -> scala/None.
[8:46..8:49): ??? -> scala/Predef.`???`().
[8:29..8:32): ??? -> scala/Predef.`???`().
[10:7..10:14): leftVar <- example/ValPattern#leftVar().
[10:16..10:24): rightVar <- example/ValPattern#rightVar().
[11:6..11:10): Some -> scala/Some.
Expand Down

0 comments on commit c5181b9

Please sign in to comment.