Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/Message.scala
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,14 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self =>
def mapMsg(f: String => String): Message = new Message(errorId):
val kind = self.kind
def msg(using Context) = f(self.msg)
override def msgPostscript(using Context) = self.msgPostscript
def explain(using Context) = self.explain
override def canExplain = self.canExplain

def appendExplanation(suffix: => String): Message = new Message(errorId):
val kind = self.kind
def msg(using Context) = self.msg
override def msgPostscript(using Context) = self.msgPostscript
def explain(using Context) = self.explain ++ suffix
override def canExplain = true

Expand Down
28 changes: 25 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import util.chaining.tap

import collection.mutable
import config.Printers.{overload, typr, unapp}
import inlines.Inlines
import TypeApplications.*
import Annotations.Annotation

Expand All @@ -39,7 +40,6 @@ import annotation.threadUnsafe
import scala.annotation.tailrec
import scala.util.control.NonFatal
import dotty.tools.dotc.cc.isRetains
import dotty.tools.dotc.inlines.Inlines

object Applications {
import tpd.*
Expand Down Expand Up @@ -1032,11 +1032,33 @@ trait Applications extends Compatibility {
isPureExpr(arg)
|| arg.isInstanceOf[RefTree | Apply | TypeApply] && arg.symbol.name.is(DefaultGetterName)

val result: Tree = {
def defaultsAddendum(args: List[Tree]): Unit =
def usesDefault(arg: Tree): Boolean = arg match
case TypeApply(Select(_, name), _) => name.is(DefaultGetterName)
case Apply(Select(_, name), _) => name.is(DefaultGetterName)
case Apply(fun, _) => usesDefault(fun)
case _ => false
ctx.reporter.mapBufferedMessages:
case dia: Diagnostic.Error =>
dia.msg match
case msg: TypeMismatch
if msg.inTree.exists: t =>
args.exists(arg => arg.span == t.span && usesDefault(arg))
=>
val noteText = i"Error occurred in an application involving default arguments."
val explained = i"Expanded application: ${cpy.Apply(app)(normalizedFun, args)}"
Diagnostic.Error(msg.append(s"\n$noteText").appendExplanation(s"\n\n$explained"), dia.pos)
case msg => dia
case dia => dia

val result: Tree = {
var typedArgs = typedArgBuf.toList
def app0 = cpy.Apply(app)(normalizedFun, typedArgs) // needs to be a `def` because typedArgs can change later
val app1 =
if !success then app0.withType(UnspecifiedErrorType)
if !success then
if typedArgs.exists(_.tpe.isError) then
defaultsAddendum(typedArgs)
app0.withType(UnspecifiedErrorType)
else {
if isJavaAnnotConstr(methRef.symbol) then
// #19951 Make sure all arguments are NamedArgs for Java annotations
Expand Down
20 changes: 14 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1054,8 +1054,9 @@ trait Checking {
}

/** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`.
* This means `sel` is either marked @unchecked or `sel.tpe` conforms to the
* pattern's type. If pattern is an UnApply, also check that the extractor is
* This means `sel` is either marked `: @RuntimeChecked`, `: @unchecked` (old style),
* or `sel.tpe` conforms to the pattern's type. If pattern is an Unapply,
* also check that the extractor is
* irrefutable, and do the check recursively.
*/
def checkIrrefutable(sel: Tree, pat: Tree, isPatDef: Boolean)(using Context): Boolean = {
Expand All @@ -1068,7 +1069,7 @@ trait Checking {
import Reason.*
val message = reason match
case NonConforming =>
var reportedPt = pt.dropAnnot(defn.UncheckedAnnot)
var reportedPt = pt.dropAnnot(defn.UncheckedAnnot).dropAnnot(defn.RuntimeCheckedAnnot)
if !pat.tpe.isSingleton then reportedPt = reportedPt.widen
val problem = if pat.tpe <:< reportedPt then "is more specialized than" else "does not match"
em"pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt"
Expand All @@ -1084,7 +1085,10 @@ trait Checking {
else em"pattern binding uses refutable extractor `$extractor`"

val fix =
if isPatDef then "adding `: @unchecked` after the expression"
if isPatDef then
val patchText =
if sourceVersion.isAtLeast(`3.8`) then ".runtimeChecked" else ": @unchecked"
s"adding `$patchText` after the expression"
else "adding the `case` keyword before the full pattern"
val addendum =
if isPatDef then "may result in a MatchError at runtime"
Expand All @@ -1097,7 +1101,9 @@ trait Checking {
case NonConforming => sel.srcPos
case RefutableExtractor => pat.source.atSpan(pat.span `union` sel.span)
else pat.srcPos
def rewriteMsg = Message.rewriteNotice("This patch", `3.2-migration`)
def rewriteMsg = Message.rewriteNotice("This patch",
if isPatDef && sourceVersion.isAtLeast(`3.8`) then `3.8-migration` else `3.2-migration`
)
report.errorOrMigrationWarning(
message.append(
i"""|
Expand All @@ -1106,7 +1112,7 @@ trait Checking {
|which $addendum.$rewriteMsg"""),
pos,
// we tighten for-comprehension without `case` to error in 3.4,
// but we keep pat-defs as warnings for now ("@unchecked"),
// but we keep pat-defs as warnings for now (".runtimeChecked"),
// until we propose an alternative way to assert exhaustivity to the typechecker.
if isPatDef then MigrationVersion.ForComprehensionUncheckedPathDefs
else MigrationVersion.ForComprehensionPatternWithoutCase
Expand All @@ -1129,6 +1135,8 @@ trait Checking {
!sourceVersion.isAtLeast(`3.2`)
|| pt.hasAnnotation(defn.UncheckedAnnot)
|| pt.hasAnnotation(defn.RuntimeCheckedAnnot)
|| pat.tpe.isErroneous // avoid spurious warning
|| pt.isErroneous
|| {
patmatch.println(i"check irrefutable $pat: ${pat.tpe} against $pt")
pat match
Expand Down
18 changes: 11 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2180,10 +2180,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
typedMatchFinish(tree, sel1, selType, tree.cases, pt)
}

/** Are some form of brackets necessary to annotate the tree `sel` as `@unchecked`?
/** Are some form of brackets necessary to annotate the tree `sel` as `.runtimeChecked`?
* If so, return a Some(opening bracket, closing bracket), otherwise None.
*/
def uncheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match
def runtimeCheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match
case _: untpd.If
| _: untpd.Match
| _: untpd.ForYield
Expand All @@ -2202,20 +2202,24 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
&& sourceVersion.isAtLeast(`3.2`)
&& sourceVersion.isMigrating
then
if isPatDef then uncheckedBrackets(tree.selector) match
if isPatDef then
val patchText =
if sourceVersion.isAtLeast(`3.8`) then ".runtimeChecked"
else ": @unchecked"
runtimeCheckedBrackets(tree.selector) match
case None =>
patch(Span(tree.selector.span.end), ": @unchecked")
patch(Span(tree.selector.span.end), patchText)
case Some(bl, br) =>
patch(Span(tree.selector.span.start), s"$bl")
patch(Span(tree.selector.span.end), s"$br: @unchecked")
patch(Span(tree.selector.span.end), s"$br$patchText")
else
patch(Span(tree.span.start), "case ")

// skip exhaustivity check in later phase
// TODO: move the check above to patternMatcher phase
val uncheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.UncheckedAnnot, tree.selector.span))
val runtimeCheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.RuntimeCheckedAnnot, tree.selector.span))
tpd.cpy.Match(result)(
selector = tpd.Typed(sel, tpd.TypeTree(uncheckedTpe, inferred = true)),
selector = tpd.Typed(sel, tpd.TypeTree(runtimeCheckedTpe, inferred = true)),
cases = result.cases
)
case _ =>
Expand Down
3 changes: 2 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ class CompilationTests {
compileFile("tests/rewrites/private-this.scala", defaultOptions.and("-rewrite", "-source", "future-migration")),
compileFile("tests/rewrites/alphanumeric-infix-operator.scala", defaultOptions.and("-rewrite", "-source", "future-migration")),
compileFile("tests/rewrites/filtering-fors.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")),
compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")),
compileFile("tests/rewrites/refutable-pattern-bindings-old.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")),
compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "3.8-migration")),
compileFile("tests/rewrites/i8982.scala", defaultOptions.and("-indent", "-rewrite")),
compileFile("tests/rewrites/i9632.scala", defaultOptions.and("-indent", "-rewrite")),
compileFile("tests/rewrites/i11895.scala", defaultOptions.and("-indent", "-rewrite")),
Expand Down
8 changes: 4 additions & 4 deletions tests/neg/i11118.check
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
-- Warning: tests/neg/i11118.scala:2:12 --------------------------------------------------------------------------------
2 |val (a,b) = (1,2,3) // error // warning
| ^^^^^^^
| pattern's type (Any, Any) does not match the right hand side expression's type (Int, Int, Int)
| pattern's type (Any, Any) does not match the right hand side expression's type (Int, Int, Int)
|
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
| If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
-- Error: tests/neg/i11118.scala:2:4 -----------------------------------------------------------------------------------
2 |val (a,b) = (1,2,3) // error // warning
| ^
Expand Down
48 changes: 48 additions & 0 deletions tests/neg/refutable-pattern-binding-messages-old.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
-- Error: tests/neg/refutable-pattern-binding-messages-old.scala:6:14 --------------------------------------------------
6 | for Positive(i) <- List(1, 2, 3) do () // error: refutable extractor
| ^^^^^^^^^^^
| pattern binding uses refutable extractor `Test.Positive`
|
| If this usage is intentional, this can be communicated by adding the `case` keyword before the full pattern,
| which will result in a filtering for expression (using `withFilter`).
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
-- Error: tests/neg/refutable-pattern-binding-messages-old.scala:11:11 -------------------------------------------------
11 | for ((x: String) <- xs) do () // error: pattern type more specialized
| ^^^^^^
| pattern's type String is more specialized than the right hand side expression's type AnyRef
|
| If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern,
| which will result in a filtering for expression (using `withFilter`).
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
-- Error: tests/neg/refutable-pattern-binding-messages-old.scala:15:13 -------------------------------------------------
15 | for none @ None <- ys do () // error: pattern type does not match
| ^^^^
| pattern's type None.type does not match the right hand side expression's type (x$1 : Option[?])
|
| If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern,
| which will result in a filtering for expression (using `withFilter`).
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:5:14 ------------------------------------------------
5 | val Positive(p) = 5 // warn: refutable extractor
| ^^^^^^^^^^^^^^^
| pattern binding uses refutable extractor `Test.Positive`
|
| If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:10:20 -----------------------------------------------
10 | val i :: is = List(1, 2, 3) // warn: pattern type more specialized
| ^^^^^^^^^^^^^
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
|
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:16:10 -----------------------------------------------
16 | val 1 = 2 // warn: pattern type does not match
| ^
| pattern's type (1 : Int) does not match the right hand side expression's type (2 : Int)
|
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
17 changes: 17 additions & 0 deletions tests/neg/refutable-pattern-binding-messages-old.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//> using options -source 3.7
object Test {
// refutable extractor
object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) }
val Positive(p) = 5 // warn: refutable extractor
for Positive(i) <- List(1, 2, 3) do () // error: refutable extractor

// more specialized
val xs: List[AnyRef] = ???
val i :: is = List(1, 2, 3) // warn: pattern type more specialized
for ((x: String) <- xs) do () // error: pattern type more specialized

// does not match
val ys: List[Option[?]] = ???
for none @ None <- ys do () // error: pattern type does not match
val 1 = 2 // warn: pattern type does not match
}
20 changes: 10 additions & 10 deletions tests/neg/refutable-pattern-binding-messages.check
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,22 @@
| ^^^^^^^^^^^^^^^
| pattern binding uses refutable extractor `Test.Positive`
|
| If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression,
| If this usage is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
-- Warning: tests/neg/refutable-pattern-binding-messages.scala:10:20 ---------------------------------------------------
10 | val i :: is = List(1, 2, 3) // warn: pattern type more specialized
| ^^^^^^^^^^^^^
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
|
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
| If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
-- Warning: tests/neg/refutable-pattern-binding-messages.scala:16:10 ---------------------------------------------------
16 | val 1 = 2 // warn: pattern type does not match
| ^
| pattern's type (1 : Int) does not match the right hand side expression's type (2 : Int)
| pattern's type (1 : Int) does not match the right hand side expression's type (2 : Int)
|
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
| If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
2 changes: 1 addition & 1 deletion tests/neg/refutable-pattern-binding-messages.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

//> using options -source 3.8
object Test {
// refutable extractor
object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) }
Expand Down
Loading
Loading