Skip to content

Commit a1c20d5

Browse files
vitorsvieiraallanrenucci
authored andcommitted
Fix #3540: Implicit class with two arguments does not fail compilation (#3641)
1 parent e773e7b commit a1c20d5

File tree

7 files changed

+57
-3
lines changed

7 files changed

+57
-3
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,11 @@ object desugar {
285285

286286
val isCaseClass = mods.is(Case) && !mods.is(Module)
287287
val isCaseObject = mods.is(Case) && mods.is(Module)
288+
val isImplicit = mods.is(Implicit)
288289
val isEnum = mods.hasMod[Mod.Enum] && !mods.is(Module)
289290
val isEnumCase = isLegalEnumCase(cdef)
290291
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
291-
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
292+
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
292293

293294

294295
val originalTparams = constr1.tparams
@@ -505,7 +506,7 @@ object desugar {
505506
// synthetic implicit C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, ..., pMN: TMN): C[Ts] =
506507
// new C[Ts](p11, ..., p1N) ... (pM1, ..., pMN) =
507508
val implicitWrappers =
508-
if (!mods.is(Implicit))
509+
if (!isImplicit)
509510
Nil
510511
else if (ctx.owner is Package) {
511512
ctx.error(TopLevelImplicitClass(cdef), cdef.pos)
@@ -515,6 +516,10 @@ object desugar {
515516
ctx.error(ImplicitCaseClass(cdef), cdef.pos)
516517
Nil
517518
}
519+
else if (arity != 1) {
520+
ctx.error(ImplicitClassPrimaryConstructorArity(), cdef.pos)
521+
Nil
522+
}
518523
else
519524
// implicit wrapper is typechecked in same scope as constructor, so
520525
// we can reuse the constructor parameters; no derived params are needed.

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public enum ErrorMessageID {
2020
EarlyDefinitionsNotSupportedID,
2121
TopLevelImplicitClassID,
2222
ImplicitCaseClassID,
23+
ImplicitClassPrimaryConstructorArityID,
2324
ObjectMayNotHaveSelfTypeID,
2425
TupleTooLongID,
2526
RepeatedModifierID,

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import printing.Formatting
2020
import ErrorMessageID._
2121
import Denotations.SingleDenotation
2222
import dotty.tools.dotc.ast.Trees
23-
import dotty.tools.dotc.ast.untpd.Modifiers
2423
import dotty.tools.dotc.config.ScalaVersion
2524
import dotty.tools.dotc.core.Flags.{FlagSet, Mutable}
2625
import dotty.tools.dotc.core.SymDenotations.SymDenotation
@@ -453,6 +452,22 @@ object messages {
453452
|""" + implicitClassRestrictionsText
454453
}
455454

455+
case class ImplicitClassPrimaryConstructorArity()(implicit ctx: Context)
456+
extends Message(ImplicitClassPrimaryConstructorArityID){
457+
val kind = "Syntax"
458+
val msg = "Implicit classes must accept exactly one primary constructor parameter"
459+
val explanation = {
460+
val example = "implicit class RichDate(date: java.util.Date)"
461+
hl"""Implicit classes may only take one non-implicit argument in their constructor. For example:
462+
|
463+
| $example
464+
|
465+
|While it’s possible to create an implicit class with more than one non-implicit argument,
466+
|such classes aren’t used during implicit lookup.
467+
|""" + implicitClassRestrictionsText
468+
}
469+
}
470+
456471
case class ObjectMayNotHaveSelfType(mdef: untpd.ModuleDef)(implicit ctx: Context)
457472
extends Message(ObjectMayNotHaveSelfTypeID) {
458473
val kind = "Syntax"

compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,21 @@ class ErrorMessagesTests extends ErrorMessagesTest {
801801
assertEquals(err, ExpectedClassOrObjectDef())
802802
}
803803

804+
@Test def implicitClassPrimaryConstructorArity =
805+
checkMessagesAfter("frontend") {
806+
"""
807+
|object Test {
808+
| implicit class Foo(i: Int, s: String)
809+
|}
810+
""".stripMargin
811+
}
812+
.expect { (itcx, messages) =>
813+
implicit val ctx: Context = itcx
814+
assertMessageCount(1, messages)
815+
val err :: Nil = messages
816+
assertEquals(err, ImplicitClassPrimaryConstructorArity())
817+
}
818+
804819
@Test def anonymousFunctionMissingParamType =
805820
checkMessagesAfter("refchecks") {
806821
"""

tests/neg/i2464.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Foo {
2+
object Bar
3+
implicit class Bar // error: Implicit classes must accept exactly one primary constructor parameter
4+
}

tests/neg/i3540.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object Test {
2+
implicit class Foo(i: Int, s: String) // error: Implicit classes must accept exactly one primary constructor parameter
3+
implicit class Foo0 // error: Implicit classes must accept exactly one primary constructor parameter
4+
implicit class Foo1() // error: Implicit classes must accept exactly one primary constructor parameter
5+
implicit class Foo2()(x: Int) // error: Implicit classes must accept exactly one primary constructor parameter
6+
implicit case class Bar0 // error: A case class must have at least one parameter list
7+
implicit case class Bar1() // error: A case class may not be defined as implicit
8+
implicit case class Bar2()(x: Int) // error: A case class may not be defined as implicit
9+
}

tests/pos/i3540.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test {
2+
implicit class Foo(x: Int)(implicit y: Int)
3+
implicit class Foo0(x: Int)(y: Int)(implicit z: Int) // OK but not used during implicit lookup
4+
implicit class Foo1(x: Int)(y: Int) // OK but not used during implicit lookup
5+
}

0 commit comments

Comments
 (0)