Skip to content

Commit ae96301

Browse files
committed
Refine by-name handling and add test
1 parent df71c9a commit ae96301

File tree

5 files changed

+143
-14
lines changed

5 files changed

+143
-14
lines changed

compiler/src/dotty/tools/dotc/config/Printers.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ object Printers {
3030
val lexical: Printer = noPrinter
3131
val inlining: Printer = noPrinter
3232
val interactiv: Printer = noPrinter
33+
val nullables: Printer = noPrinter
3334
val overload: Printer = noPrinter
3435
val patmatch: Printer = noPrinter
3536
val pickling: Printer = noPrinter

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import transform.TypeUtils._
2525
import Nullables.given
2626

2727
import collection.mutable
28-
import config.Printers.{overload, typr, unapp}
28+
import config.Printers.{overload, typr, unapp, nullables}
2929
import TypeApplications._
3030

3131
import reporting.diagnostic.Message
@@ -1059,28 +1059,45 @@ trait Applications extends Compatibility {
10591059
*/
10601060
private def postProcessByNameArgs(fn: TermRef, app: Tree)(given ctx: Context): Tree =
10611061
fn.widen match
1062-
case mt: MethodType if mt.paramInfos.exists.isInstanceOf[ExprType] =>
1062+
case mt: MethodType if mt.paramInfos.exists(_.isInstanceOf[ExprType]) =>
10631063
app match
10641064
case Apply(fn, args) =>
10651065
val dropNotNull = new TreeMap with
10661066
override def transform(t: Tree)(given Context) = t match
1067-
case AssertNotNull(t0) if t0.symbol.is(Mutable) => transform(t0)
1067+
case AssertNotNull(t0) if t0.symbol.is(Mutable) =>
1068+
nullables.println(i"dropping $t")
1069+
transform(t0)
10681070
case t: ValDef if !t.symbol.is(Lazy) => super.transform(t)
1069-
case t: MemberDef => t // stop here since embedded references are out of order anyway
1070-
case t => super.transform(t)
1071+
case t: MemberDef =>
1072+
// stop here since embedded references to mutable variables would be
1073+
// out of order, so they would not asserted ot be not-null anyway.
1074+
// @see Nullables.usedOutOfOrder
1075+
t
1076+
case _ => super.transform(t)
1077+
1078+
object retyper extends ReTyper with
1079+
override def typedUnadapted(t: untpd.Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = t match
1080+
case t: ValDef if !t.symbol.is(Lazy) => super.typedUnadapted(t, pt, locked)
1081+
case t: MemberDef => promote(t)
1082+
case _ => super.typedUnadapted(t, pt, locked)
10711083

10721084
def postProcess(formal: Type, arg: Tree): Tree =
10731085
val arg1 = dropNotNull.transform(arg)
1074-
if (arg1 ne arg) && !(arg1.tpe <:< formal) then
1075-
ctx.error(em"""This argument was typed using flow assumptions about mutable variables
1076-
|but it is passed to a by-name parameter where such flow assumptions are unsound.
1077-
|Wrapping the argument in `byName(...)` fixes the problem by disabling the flow assumptions.
1078-
|
1079-
|`byName` needs to be imported from the `scala.compiletime` package.""",
1080-
arg.sourcePos)
1081-
arg
1086+
if arg1 eq arg then arg
10821087
else
1083-
arg1
1088+
val nestedCtx = ctx.fresh.setNewTyperState()
1089+
val arg2 = retyper.typed(arg1, formal)(given nestedCtx)
1090+
if nestedCtx.reporter.hasErrors || !(arg2.tpe <:< formal) then
1091+
ctx.error(em"""This argument was typed using flow assumptions about mutable variables
1092+
|but it is passed to a by-name parameter where such flow assumptions are unsound.
1093+
|Wrapping the argument in `byName(...)` fixes the problem by disabling the flow assumptions.
1094+
|
1095+
|`byName` needs to be imported from the `scala.compiletime` package.""",
1096+
arg.sourcePos)
1097+
arg
1098+
else
1099+
nestedCtx.typerState.commit()
1100+
arg2
10841101

10851102
def recur(formals: List[Type], args: List[Tree]): List[Tree] = (formals, args) match
10861103
case (formal :: formalsRest, arg :: argsRest) =>

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class CompilationTests extends ParallelTesting {
115115
compileFilesInDir("tests/neg-custom-args/deprecation", defaultOptions.and("-Xfatal-warnings", "-deprecation")),
116116
compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")),
117117
compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings),
118+
compileFilesInDir("tests/neg-custom-args/explicit-nulls", defaultOptions.and("-Yexplicit-nulls")),
118119
compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")),
119120
compileFile("tests/neg-custom-args/implicit-conversions.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
120121
compileFile("tests/neg-custom-args/implicit-conversions-old.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/explicit-nulls/byname-nullables.scala:19:24 -----------------------
2+
19 | if x != null then f(x) // error: f is call-by-name
3+
| ^
4+
| Found: (x : String | Null)
5+
| Required: String
6+
-- Error: tests/neg-custom-args/explicit-nulls/byname-nullables.scala:43:32 --------------------------------------------
7+
43 | if x != null then f(identity(x), 1) // error: dropping not null check fails typing
8+
| ^^^^^^^^^^^
9+
| This argument was typed using flow assumptions about mutable variables
10+
| but it is passed to a by-name parameter where such flow assumptions are unsound.
11+
| Wrapping the argument in `byName(...)` fixes the problem by disabling the flow assumptions.
12+
|
13+
| `byName` needs to be imported from the `scala.compiletime` package.
14+
-- Error: tests/neg-custom-args/explicit-nulls/byname-nullables.scala:68:24 --------------------------------------------
15+
68 | if x != null then f(x, 1) // error: dropping not null check typechecks OK, but gives incompatible result type
16+
| ^
17+
| This argument was typed using flow assumptions about mutable variables
18+
| but it is passed to a by-name parameter where such flow assumptions are unsound.
19+
| Wrapping the argument in `byName(...)` fixes the problem by disabling the flow assumptions.
20+
|
21+
| `byName` needs to be imported from the `scala.compiletime` package.
22+
-- [E134] Type Mismatch Error: tests/neg-custom-args/explicit-nulls/byname-nullables.scala:81:22 -----------------------
23+
81 | if x != null then f(byName(x), 1) // error: none of the overloaded methods match argument types
24+
| ^
25+
| None of the overloaded alternatives of method f in object Test7 with types
26+
| (x: => String, y: Int): String
27+
| (x: String, y: String): String
28+
| match arguments (String | Null, (1 : Int))
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
object Test1 with
2+
3+
def f(x: String) =
4+
x ++ x
5+
6+
def g() =
7+
var x: String | Null = "abc"
8+
if x != null then f(x) // OK: f is call-by-value
9+
else x
10+
11+
12+
object Test2 with
13+
14+
def f(x: => String) =
15+
x ++ x
16+
17+
def g() =
18+
var x: String | Null = "abc"
19+
if x != null then f(x) // error: f is call-by-name
20+
else x
21+
22+
object Test3 with
23+
24+
def f(x: String, y: String) = x
25+
26+
def f(x: => String | Null, y: Int) =
27+
x
28+
29+
def g() =
30+
var x: String | Null = "abc"
31+
if x != null then f(x, 1) // OK: not-null check successfully dropped
32+
else x
33+
34+
object Test4 with
35+
36+
def f(x: String, y: String) = x
37+
38+
def f(x: => String | Null, y: Int) =
39+
x
40+
41+
def g() =
42+
var x: String | Null = "abc"
43+
if x != null then f(identity(x), 1) // error: dropping not null check fails typing
44+
else x
45+
46+
object Test5 with
47+
import compiletime.byName
48+
49+
def f(x: String, y: String) = x
50+
51+
def f(x: => String | Null, y: Int) =
52+
x
53+
54+
def g() =
55+
var x: String | Null = "abc"
56+
if x != null then f(byName(identity(x)), 1) // OK, byName avoids the flow typing
57+
else x
58+
59+
object Test6 with
60+
61+
def f(x: String, y: String) = x
62+
63+
def f(x: => String, y: Int) =
64+
x
65+
66+
def g() =
67+
var x: String | Null = "abc"
68+
if x != null then f(x, 1) // error: dropping not null check typechecks OK, but gives incompatible result type
69+
else x
70+
71+
object Test7 with
72+
import compiletime.byName
73+
74+
def f(x: String, y: String) = x
75+
76+
def f(x: => String, y: Int) =
77+
x
78+
79+
def g() =
80+
var x: String | Null = "abc"
81+
if x != null then f(byName(x), 1) // error: none of the overloaded methods match argument types
82+
else x

0 commit comments

Comments
 (0)