Skip to content

Commit fe40c59

Browse files
committed
Fix Scala Wart about implicit () class parameters
Fixes #2576 As the discussion in #2576 shows, we still have some problems with the implicitly inserted empty parameter lists for class constructors. We do need that empty list to support syntax like `C()` and `new C()`. But it gets in the way if a class has using clauses. Example from the issue: ```scala class Bar(using x: Int)(y: String) given Int = ??? def test = new Bar("") ``` Here, an implicitly inserted `()` in front makes the last line fail. We'd need `new Bar()("")`. If a class has only using clauses as parameters we now insert a `()` at the end instead of at the start. That makes the example compile. For old-style implicit parameters we don't have a choice. We still need the `()` at the start since otherwise we'd change the meaning of calls with explicit arguments for the implicit parameters.
1 parent 4a96ce7 commit fe40c59

File tree

10 files changed

+67
-40
lines changed

10 files changed

+67
-40
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+9-5
Original file line numberDiff line numberDiff line change
@@ -585,12 +585,16 @@ object desugar {
585585

586586
// new C[Ts](paramss)
587587
lazy val creatorExpr = {
588-
val vparamss = constrVparamss match {
589-
case (vparam :: _) :: _ if vparam.mods.isOneOf(GivenOrImplicit) => // add a leading () to match class parameters
588+
val vparamss = constrVparamss match
589+
case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters
590590
Nil :: constrVparamss
591591
case _ =>
592-
constrVparamss
593-
}
592+
if constrVparamss.nonEmpty && constrVparamss.forall {
593+
case vparam :: _ => vparam.mods.is(Given)
594+
case _ => false
595+
}
596+
then constrVparamss :+ Nil // add a trailing () to match class parameters
597+
else constrVparamss
594598
val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) =>
595599
val app = Apply(nu, vparams.map(refOfDef))
596600
vparams match {
@@ -818,7 +822,7 @@ object desugar {
818822
}
819823

820824
flatTree(cdef1 :: companions ::: implicitWrappers ::: enumScaffolding)
821-
}.showing(i"desugared: $result", Printers.desugar)
825+
}.showing(i"desugared: $cdef --> $result", Printers.desugar)
822826

823827
/** Expand
824828
*

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ object PathResolver {
131131

132132
def fromPathString(path: String)(using Context): ClassPath = {
133133
val settings = ctx.settings.classpath.update(path)
134-
new PathResolver()(using ctx.fresh.setSettings(settings)).result
134+
inContext(ctx.fresh.setSettings(settings)) {
135+
new PathResolver().result
136+
}
135137
}
136138

137139
/** Show values in Environment and Defaults when no argument is provided.
@@ -147,7 +149,9 @@ object PathResolver {
147149
val ArgsSummary(sstate, rest, errors, warnings) =
148150
ctx.settings.processArguments(args.toList, true, ctx.settingsState)
149151
errors.foreach(println)
150-
val pr = new PathResolver()(using ctx.fresh.setSettings(sstate))
152+
val pr = inContext(ctx.fresh.setSettings(sstate)) {
153+
new PathResolver()
154+
}
151155
println(" COMMAND: 'scala %s'".format(args.mkString(" ")))
152156
println("RESIDUAL: 'scala %s'\n".format(rest.mkString(" ")))
153157

compiler/src/dotty/tools/dotc/core/NamerOps.scala

+13-4
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,23 @@ object NamerOps:
1818
case TypeSymbols(tparams) :: _ => ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef))
1919
case _ => ctor.owner.typeRef
2020

21-
/** if isConstructor, make sure it has one leading non-implicit parameter list */
21+
/** If isConstructor, make sure it has at least one non-implicit parameter list
22+
* This is done by adding a () in front of a leading old style implicit parameter,
23+
* or by adding a () as last -- or only -- parameter list if the constructor has
24+
* only using clauses as parameters.
25+
*/
2226
def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean)(using Context): List[List[Symbol]] =
2327
if !isConstructor then paramss
2428
else paramss match
25-
case Nil :: _ => paramss
26-
case TermSymbols(vparam :: _) :: _ if !vparam.isOneOf(GivenOrImplicit) => paramss
2729
case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor)
28-
case _ => Nil :: paramss
30+
case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => Nil :: paramss
31+
case _ =>
32+
if paramss.forall {
33+
case TermSymbols(vparams) => vparams.nonEmpty && vparams.head.is(Given)
34+
case _ => true
35+
}
36+
then paramss :+ Nil
37+
else paramss
2938

3039
/** The method type corresponding to given parameters and result type */
3140
def methodType(paramss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(using Context): Type =

compiler/src/dotty/tools/dotc/transform/Bridges.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
170170
* time deferred methods in `stats` that are replaced by a bridge with the same signature.
171171
*/
172172
def add(stats: List[untpd.Tree]): List[untpd.Tree] =
173-
val opc = new BridgesCursor()(using preErasureCtx)
173+
val opc = inContext(preErasureCtx) { new BridgesCursor }
174174
while opc.hasNext do
175175
if !opc.overriding.is(Deferred) then
176176
addBridgeIfNeeded(opc.overriding, opc.overridden)

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

+22-15
Original file line numberDiff line numberDiff line change
@@ -2356,25 +2356,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
23562356

23572357
/** If `ref` is an implicitly parameterized trait, pass an implicit argument list.
23582358
* Otherwise, if `ref` is a parameterized trait, error.
2359-
* Note: Traits and classes currently always have at least an empty parameter list ()
2360-
* before the implicit parameters (this is inserted if not given in source).
2361-
* We skip this parameter list when deciding whether a trait is parameterless or not.
2359+
* Note: Traits and classes have sometimes a synthesized empty parameter list ()
2360+
* in front or after the implicit parameter(s). See NamerOps.normalizeIfConstructor.
2361+
* We synthesize a () argument at the correct place in this case.
23622362
* @param ref The tree referring to the (parent) trait
23632363
* @param psym Its type symbol
2364-
* @param cinfo The info of its constructor
23652364
*/
2366-
def maybeCall(ref: Tree, psym: Symbol): Tree = psym.primaryConstructor.info.stripPoly match
2367-
case cinfo @ MethodType(Nil) if cinfo.resultType.isImplicitMethod =>
2365+
def maybeCall(ref: Tree, psym: Symbol): Tree =
2366+
def appliedRef =
23682367
typedExpr(untpd.New(untpd.TypedSplice(ref)(using superCtx), Nil))(using superCtx)
2369-
case cinfo @ MethodType(Nil) if !cinfo.resultType.isInstanceOf[MethodType] =>
2370-
ref
2371-
case cinfo: MethodType =>
2372-
if !ctx.erasedTypes then // after constructors arguments are passed in super call.
2373-
typr.println(i"constr type: $cinfo")
2374-
report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos)
2375-
ref
2376-
case _ =>
2377-
ref
2368+
def dropContextual(tp: Type): Type = tp.stripPoly match
2369+
case mt: MethodType if mt.isContextualMethod => dropContextual(mt.resType)
2370+
case _ => tp
2371+
psym.primaryConstructor.info.stripPoly match
2372+
case cinfo @ MethodType(Nil)
2373+
if cinfo.resultType.isImplicitMethod && !cinfo.resultType.isContextualMethod =>
2374+
appliedRef
2375+
case cinfo =>
2376+
val cinfo1 = dropContextual(cinfo)
2377+
cinfo1 match
2378+
case cinfo1 @ MethodType(Nil) if !cinfo1.resultType.isInstanceOf[MethodType] =>
2379+
if cinfo1 ne cinfo then appliedRef else ref
2380+
case cinfo1: MethodType if !ctx.erasedTypes =>
2381+
report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos)
2382+
ref
2383+
case _ =>
2384+
ref
23782385

23792386
val seenParents = mutable.Set[Symbol]()
23802387

tests/neg/i12344.scala

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import scala.quoted.*
22

33
class C(using q: Quotes)(i: Int = 1, f: q.reflect.Flags = q.reflect.Flags.EmptyFlags)
44

5-
def test1a(using q: Quotes) = new C() // error
6-
def test2a(using q: Quotes) = new C(1) // error
7-
def test3a(using q: Quotes) = new C(1, q.reflect.Flags.Lazy) // error
8-
def test4a(using q: Quotes) = new C(f = q.reflect.Flags.Lazy) // error
5+
def test1a(using q: Quotes) = new C()
6+
def test2a(using q: Quotes) = new C(1)
7+
def test3a(using q: Quotes) = new C(1, q.reflect.Flags.Lazy)
8+
def test4a(using q: Quotes) = new C(f = q.reflect.Flags.Lazy)
99

10-
def test1b(using q: Quotes) = C() // error
11-
def test2b(using q: Quotes) = C(1) // error
12-
def test3b(using q: Quotes) = C(1, q.reflect.Flags.Lazy) // error
13-
def test4b(using q: Quotes) = C(f = q.reflect.Flags.Lazy) // error
10+
def test1b(using q: Quotes) = C()
11+
def test2b(using q: Quotes) = C(1)
12+
def test3b(using q: Quotes) = C(1, q.reflect.Flags.Lazy)
13+
def test4b(using q: Quotes) = C(f = q.reflect.Flags.Lazy)
1414

1515
def test1c(using q: Quotes) = new C(using q)()
1616
def test2c(using q: Quotes) = new C(using q)(1)
@@ -22,5 +22,5 @@ def test2d(using q: Quotes) = C(using q)(1)
2222
def test3d(using q: Quotes) = C(using q)(1, q.reflect.Flags.Lazy)
2323
def test4d(using q: Quotes) = C(using q)(f = q.reflect.Flags.Lazy)
2424

25-
def test1e(using q: Quotes) = new C()()
26-
def test2e(using q: Quotes) = C()()
25+
def test1e(using q: Quotes) = new C()() // error
26+
def test2e(using q: Quotes) = C()() // error

tests/pos/given-constrapps.scala

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ class Foo(using TC) {
1919

2020
object Test extends App {
2121
new C(using tc)
22-
new C()(using tc)
2322
new C(using tc) {}
2423
new C2(1)(using tc)(using List(tc))
2524
new C2(1)(using tc)(using List(tc)) {}

tests/pos/i2576.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Bar(using x: Int)(y: String)
2+
given Int = ???
3+
def test = new Bar("")

tests/run-macros/i12021/Macro_1.scala

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ def inspect2[A: Type](using Quotes): Expr[String] = {
99
val ps =
1010
TypeRepr.of[A].typeSymbol.primaryConstructor.tree match
1111
case DefDef(_, List(Nil, ps: TermParamClause), _, _) => ps
12+
case DefDef(_, List(ps: TermParamClause, Nil), _, _) => ps
1213
case DefDef(_, List(ps: TermParamClause), _, _) => ps
1314

1415
val names = ps.params.map(p => s"${p.name}: ${p.tpt.show}").mkString("(", ", ", ")")

tests/run/i2567.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object Test extends App {
1010
new Foo
1111
new Foo(using tc)
1212
new Foo()
13-
new Foo()(using tc)
13+
new Foo(using tc)
1414
Foo()
15-
Foo()(using tc)
15+
Foo(using tc)
1616
}

0 commit comments

Comments
 (0)