Skip to content

Fix #3540: Implicit class with two arguments does not fail compilation #3641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,11 @@ object desugar {

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


val originalTparams = constr1.tparams
Expand Down Expand Up @@ -505,7 +506,7 @@ object desugar {
// synthetic implicit C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, ..., pMN: TMN): C[Ts] =
// new C[Ts](p11, ..., p1N) ... (pM1, ..., pMN) =
val implicitWrappers =
if (!mods.is(Implicit))
if (!isImplicit)
Nil
else if (ctx.owner is Package) {
ctx.error(TopLevelImplicitClass(cdef), cdef.pos)
Expand All @@ -515,6 +516,10 @@ object desugar {
ctx.error(ImplicitCaseClass(cdef), cdef.pos)
Nil
}
else if (arity != 1) {
ctx.error(ImplicitClassPrimaryConstructorArity(), cdef.pos)
Nil
}
else
// implicit wrapper is typechecked in same scope as constructor, so
// we can reuse the constructor parameters; no derived params are needed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum ErrorMessageID {
EarlyDefinitionsNotSupportedID,
TopLevelImplicitClassID,
ImplicitCaseClassID,
ImplicitClassPrimaryConstructorArityID,
ObjectMayNotHaveSelfTypeID,
TupleTooLongID,
RepeatedModifierID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import printing.Formatting
import ErrorMessageID._
import Denotations.SingleDenotation
import dotty.tools.dotc.ast.Trees
import dotty.tools.dotc.ast.untpd.Modifiers
import dotty.tools.dotc.config.ScalaVersion
import dotty.tools.dotc.core.Flags.{FlagSet, Mutable}
import dotty.tools.dotc.core.SymDenotations.SymDenotation
Expand Down Expand Up @@ -453,6 +452,22 @@ object messages {
|""" + implicitClassRestrictionsText
}

case class ImplicitClassPrimaryConstructorArity()(implicit ctx: Context)
extends Message(ImplicitClassPrimaryConstructorArityID){
val kind = "Syntax"
val msg = "Implicit classes must accept exactly one primary constructor parameter"
val explanation = {
val example = "implicit class RichDate(date: java.util.Date)"
hl"""Implicit classes may only take one non-implicit argument in their constructor. For example:
|
| $example
|
|While it’s possible to create an implicit class with more than one non-implicit argument,
|such classes aren’t used during implicit lookup.
|""" + implicitClassRestrictionsText
}
}

case class ObjectMayNotHaveSelfType(mdef: untpd.ModuleDef)(implicit ctx: Context)
extends Message(ObjectMayNotHaveSelfTypeID) {
val kind = "Syntax"
Expand Down
15 changes: 15 additions & 0 deletions compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,21 @@ class ErrorMessagesTests extends ErrorMessagesTest {
assertEquals(err, ExpectedClassOrObjectDef())
}

@Test def implicitClassPrimaryConstructorArity =
checkMessagesAfter("frontend") {
"""
|object Test {
| implicit class Foo(i: Int, s: String)
|}
""".stripMargin
}
.expect { (itcx, messages) =>
implicit val ctx: Context = itcx
assertMessageCount(1, messages)
val err :: Nil = messages
assertEquals(err, ImplicitClassPrimaryConstructorArity())
}

@Test def anonymousFunctionMissingParamType =
checkMessagesAfter("refchecks") {
"""
Expand Down
4 changes: 4 additions & 0 deletions tests/neg/i2464.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Foo {
object Bar
implicit class Bar // error: Implicit classes must accept exactly one primary constructor parameter
}
9 changes: 9 additions & 0 deletions tests/neg/i3540.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
object Test {
implicit class Foo(i: Int, s: String) // error: Implicit classes must accept exactly one primary constructor parameter
implicit class Foo0 // error: Implicit classes must accept exactly one primary constructor parameter
implicit class Foo1() // error: Implicit classes must accept exactly one primary constructor parameter
implicit class Foo2()(x: Int) // error: Implicit classes must accept exactly one primary constructor parameter
implicit case class Bar0 // error: A case class must have at least one parameter list
implicit case class Bar1() // error: A case class may not be defined as implicit
implicit case class Bar2()(x: Int) // error: A case class may not be defined as implicit
}
5 changes: 5 additions & 0 deletions tests/pos/i3540.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {
implicit class Foo(x: Int)(implicit y: Int)
implicit class Foo0(x: Int)(y: Int)(implicit z: Int) // OK but not used during implicit lookup
implicit class Foo1(x: Int)(y: Int) // OK but not used during implicit lookup
}