Skip to content

Commit 74bc3fd

Browse files
authored
Merge pull request #14840 from dotty-staging/fix-2576
Fix Scala Wart about implicit () class parameters
2 parents 7ee8986 + 0ab1bb6 commit 74bc3fd

File tree

15 files changed

+184
-44
lines changed

15 files changed

+184
-44
lines changed

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

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

590590
// new C[Ts](paramss)
591591
lazy val creatorExpr = {
592-
val vparamss = constrVparamss match {
593-
case (vparam :: _) :: _ if vparam.mods.isOneOf(GivenOrImplicit) => // add a leading () to match class parameters
592+
val vparamss = constrVparamss match
593+
case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters
594594
Nil :: constrVparamss
595595
case _ =>
596-
constrVparamss
597-
}
596+
if constrVparamss.nonEmpty && constrVparamss.forall {
597+
case vparam :: _ => vparam.mods.is(Given)
598+
case _ => false
599+
}
600+
then constrVparamss :+ Nil // add a trailing () to match class parameters
601+
else constrVparamss
598602
val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) =>
599603
val app = Apply(nu, vparams.map(refOfDef))
600604
vparams match {
@@ -822,7 +826,7 @@ object desugar {
822826
}
823827

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

827831
/** Expand
828832
*

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/core/tasty/TreeUnpickler.scala

+44-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import typer.ConstFold
2727
import typer.Checking.checkNonCyclic
2828
import typer.Nullables._
2929
import util.Spans._
30-
import util.SourceFile
30+
import util.{SourceFile, Property}
3131
import ast.{Trees, tpd, untpd}
3232
import Trees._
3333
import Decorators._
@@ -1163,6 +1163,36 @@ class TreeUnpickler(reader: TastyReader,
11631163
readPathTerm()
11641164
}
11651165

1166+
/** Adapt constructor calls where class has only using clauses from old to new scheme.
1167+
* or class has mixed using clauses and other clauses.
1168+
* Old: leading (), new: nothing, or trailing () if all clauses are using clauses.
1169+
* This is neccessary so that we can read pre-3.2 Tasty correctly. There,
1170+
* constructor calls use the old scheme, but constructor definitions already
1171+
* use the new scheme, since they are reconstituted with normalizeIfConstructor.
1172+
*/
1173+
def constructorApply(fn: Tree, args: List[Tree]): Tree =
1174+
if fn.tpe.widen.isContextualMethod && args.isEmpty then
1175+
fn.withAttachment(SuppressedApplyToNone, ())
1176+
else
1177+
val fn1 = fn match
1178+
case Apply(fn1, Nil) if fn.removeAttachment(InsertedApplyToNone).isDefined =>
1179+
// We thought we inserted a final `()` but hit a user-written `()` instead.
1180+
// Remove the inserted `()`.
1181+
fn1
1182+
case _ =>
1183+
fn
1184+
val res = tpd.Apply(fn1, args)
1185+
if fn.removeAttachment(SuppressedApplyToNone).isEmpty then
1186+
res
1187+
else res.tpe.widen match
1188+
case mt @ MethodType(params) =>
1189+
if params.isEmpty then
1190+
// Assume it's the final synthesized `()` parameter
1191+
res.appliedToNone.withAttachment(InsertedApplyToNone, ())
1192+
else if mt.isContextualMethod then
1193+
res.withAttachment(SuppressedApplyToNone, ())
1194+
else res
1195+
11661196
def readLengthTerm(): Tree = {
11671197
val end = readEnd()
11681198
val result =
@@ -1173,7 +1203,9 @@ class TreeUnpickler(reader: TastyReader,
11731203
tpd.Super(qual, mixId, mixTpe.typeSymbol)
11741204
case APPLY =>
11751205
val fn = readTerm()
1176-
tpd.Apply(fn, until(end)(readTerm()))
1206+
val args = until(end)(readTerm())
1207+
if fn.symbol.isConstructor then constructorApply(fn, args)
1208+
else tpd.Apply(fn, args)
11771209
case TYPEAPPLY =>
11781210
tpd.TypeApply(readTerm(), until(end)(readTpt()))
11791211
case TYPED =>
@@ -1560,4 +1592,14 @@ object TreeUnpickler {
15601592
inline val AllDefs = 2 // add everything
15611593

15621594
class TreeWithoutOwner extends Exception
1595+
1596+
/** An attachment key indicating that an old-style leading () in a constructor
1597+
* call that is followed by a using clause was suppressed.
1598+
*/
1599+
val SuppressedApplyToNone: Property.Key[Unit] = Property.Key()
1600+
1601+
/** An attachment key indicating that a trailing () in a constructor
1602+
* call that has otherwise only using clauses was inserted.
1603+
*/
1604+
val InsertedApplyToNone: Property.Key[Unit] = Property.Key()
15631605
}

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

+23-16
Original file line numberDiff line numberDiff line change
@@ -2381,25 +2381,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
23812381

23822382
/** If `ref` is an implicitly parameterized trait, pass an implicit argument list.
23832383
* Otherwise, if `ref` is a parameterized trait, error.
2384-
* Note: Traits and classes currently always have at least an empty parameter list ()
2385-
* before the implicit parameters (this is inserted if not given in source).
2386-
* We skip this parameter list when deciding whether a trait is parameterless or not.
2384+
* Note: Traits and classes have sometimes a synthesized empty parameter list ()
2385+
* in front or after the implicit parameter(s). See NamerOps.normalizeIfConstructor.
2386+
* We synthesize a () argument at the correct place in this case.
23872387
* @param ref The tree referring to the (parent) trait
23882388
* @param psym Its type symbol
2389-
* @param cinfo The info of its constructor
23902389
*/
2391-
def maybeCall(ref: Tree, psym: Symbol): Tree = psym.primaryConstructor.info.stripPoly match
2392-
case cinfo @ MethodType(Nil) if cinfo.resultType.isImplicitMethod =>
2390+
def maybeCall(ref: Tree, psym: Symbol): Tree =
2391+
def appliedRef =
23932392
typedExpr(untpd.New(untpd.TypedSplice(ref)(using superCtx), Nil))(using superCtx)
2394-
case cinfo @ MethodType(Nil) if !cinfo.resultType.isInstanceOf[MethodType] =>
2395-
ref
2396-
case cinfo: MethodType =>
2397-
if !ctx.erasedTypes then // after constructors arguments are passed in super call.
2398-
typr.println(i"constr type: $cinfo")
2399-
report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos)
2400-
ref
2401-
case _ =>
2402-
ref
2393+
def dropContextual(tp: Type): Type = tp.stripPoly match
2394+
case mt: MethodType if mt.isContextualMethod => dropContextual(mt.resType)
2395+
case _ => tp
2396+
psym.primaryConstructor.info.stripPoly match
2397+
case cinfo @ MethodType(Nil)
2398+
if cinfo.resultType.isImplicitMethod && !cinfo.resultType.isContextualMethod =>
2399+
appliedRef
2400+
case cinfo =>
2401+
val cinfo1 = dropContextual(cinfo)
2402+
cinfo1 match
2403+
case cinfo1 @ MethodType(Nil) if !cinfo1.resultType.isInstanceOf[MethodType] =>
2404+
if cinfo1 ne cinfo then appliedRef else ref
2405+
case cinfo1: MethodType if !ctx.erasedTypes =>
2406+
report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos)
2407+
ref
2408+
case _ =>
2409+
ref
24032410

24042411
val seenParents = mutable.Set[Symbol]()
24052412

@@ -3429,7 +3436,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34293436
def isContextBoundParams = wtp.stripPoly match
34303437
case MethodType(EvidenceParamName(_) :: _) => true
34313438
case _ => false
3432-
if sourceVersion == `future-migration` && isContextBoundParams
3439+
if sourceVersion == `future-migration` && isContextBoundParams && pt.args.nonEmpty
34333440
then // Under future-migration, don't infer implicit arguments yet for parameters
34343441
// coming from context bounds. Issue a warning instead and offer a patch.
34353442
report.migrationWarning(

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("(", ", ", ")")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Bar
2+
Bar
3+
Bar
4+
Bar
5+
()
6+
Bat
7+
Bat
8+
Bat
9+
Bat
10+
()
11+
Bax
12+
Bax
13+
Bax
14+
Bax
15+
()
16+
Baz
17+
Baz
18+
Baz
19+
Baz
20+
()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class Bar(using x: Int)(y: String):
2+
override def toString = "Bar"
3+
object Bar:
4+
given Int = 1
5+
inline def foo =
6+
println(new Bar()(""))
7+
println(Bar()(""))
8+
9+
class Bat(using x: Int):
10+
override def toString = "Bat"
11+
object Bat:
12+
given Int = 1
13+
inline def foo =
14+
println(new Bat())
15+
println(Bat())
16+
17+
class Bax(using x: Int)():
18+
override def toString = "Bax"
19+
object Bax:
20+
given Int = 1
21+
inline def foo =
22+
println(new Bax())
23+
println(Bax())
24+
25+
class Baz(using x: Int)(using y: String):
26+
override def toString = "Baz"
27+
object Baz:
28+
given Int = 1
29+
given String = "x"
30+
inline def foo =
31+
println(new Baz())
32+
println(Baz())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@main def Test =
2+
given Int = 1
3+
given String = "x"
4+
5+
println(new Bar(""))
6+
println(Bar(""))
7+
println(Bar.foo)
8+
9+
println(new Bat())
10+
println(Bat())
11+
println(Bat.foo)
12+
13+
println(new Bax())
14+
println(Bax())
15+
println(Bax.foo)
16+
17+
println(new Baz())
18+
println(Baz())
19+
println(Baz.foo)

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
}

tests/semanticdb/metac.expect

+1-1
Original file line numberDiff line numberDiff line change
@@ -1848,7 +1848,7 @@ givens/InventedNames$package.given_String. => final implicit lazy val given meth
18481848
givens/InventedNames$package.given_X. => final implicit given object given_X extends Object with X { self: given_X.type => +2 decls }
18491849
givens/InventedNames$package.given_X.doX(). => method doX => Int <: givens/X#doX().
18501850
givens/InventedNames$package.given_Y# => implicit given class given_Y extends Object with Y { self: given_Y => +3 decls }
1851-
givens/InventedNames$package.given_Y#`<init>`(). => primary ctor <init> ()(implicit val given param x$1: X): given_Y
1851+
givens/InventedNames$package.given_Y#`<init>`(). => primary ctor <init> (implicit val given param x$1: X)(): given_Y
18521852
givens/InventedNames$package.given_Y#`<init>`().(x$1) => implicit val given param x$1: X
18531853
givens/InventedNames$package.given_Y#doY(). => method doY => String <: givens/Y#doY().
18541854
givens/InventedNames$package.given_Y#x$1. => protected implicit val given method x$1 X

0 commit comments

Comments
 (0)