Skip to content

Commit 293e6c5

Browse files
committed
Introduce explicit error for non-implicit non-case parameters
Fix compilation error when case class has a method named like a case field
1 parent 4594037 commit 293e6c5

File tree

6 files changed

+124
-104
lines changed

6 files changed

+124
-104
lines changed

core/shared/src/main/scala-3.x/monocle/internal/focus/ErrorHandling.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ private[focus] trait ErrorHandling {
88
s"Cannot generate Lens for field '$fieldName', because '$fromClass' is not a case class"
99
case FocusError.NotACaseField(caseClass, fieldName) =>
1010
s"Can only create lenses for case fields, but '$fieldName' is not a case field of '$caseClass'"
11+
case FocusError.NonImplicitNonCaseParameter(caseClass, parameters) =>
12+
s"Case class '$caseClass' has non-implicit non-case parameters, which is not supported: ${parameters.map("'" + _ + "'").mkString(", ")}"
1113
case FocusError.NotAConcreteClass(fromClass) =>
1214
s"Expecting a concrete case class in the 'From' position; cannot reify type $fromClass"
1315
case FocusError.NotASimpleLambdaFunction =>
@@ -20,7 +22,7 @@ private[focus] trait ErrorHandling {
2022
case FocusError.CouldntFindFieldType(fromType, fieldName) => s"Couldn't find type for $fromType.$fieldName"
2123
case FocusError.InvalidDowncast(fromType, toType) => s"Type '$fromType' could not be cast to '$toType'"
2224
case FocusError.ImplicitNotFound(implicitType) =>
23-
s"Could not find implicit for '$implicitType'. Note: multiple non-implicit parameter sets or implicits with default values are not supported."
25+
s"Could not find implicit for '$implicitType'. Note: implicits with default values are not supported."
2426
case FocusError.ExpansionFailed(reason) =>
2527
s"Case class with multiple parameter sets could not be expanded because of: $reason"
2628
}

core/shared/src/main/scala-3.x/monocle/internal/focus/FocusBase.scala

+14-12
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@ private[focus] trait FocusBase {
77

88
given Quotes = macroContext
99

10+
type Symbol = macroContext.reflect.Symbol
1011
type Term = macroContext.reflect.Term
1112
type TypeRepr = macroContext.reflect.TypeRepr
1213

1314
case class LambdaConfig(argName: String, lambdaBody: Term)
1415

1516
enum FocusAction {
16-
case SelectField(fieldName: String, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr)
17-
case SelectFieldWithImplicits(fieldName: String, fromType: TypeRepr, toType: TypeRepr, setter: Term)
17+
case SelectField(caseFieldSymbol: Symbol, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr)
18+
case SelectFieldWithImplicits(caseFieldSymbol: Symbol, fromType: TypeRepr, toType: TypeRepr, setter: Term)
1819
case SelectOnlyField(
19-
fieldName: String,
20+
caseFieldSymbol: Symbol,
2021
fromType: TypeRepr,
2122
fromTypeArgs: List[TypeRepr],
2223
fromCompanion: Term,
2324
toType: TypeRepr
2425
)
25-
case SelectOnlyFieldWithImplicits(fieldName: String, fromType: TypeRepr, toType: TypeRepr, reverseGet: Term)
26+
case SelectOnlyFieldWithImplicits(caseFieldSymbol: Symbol, fromType: TypeRepr, toType: TypeRepr, reverseGet: Term)
2627
case KeywordSome(toType: TypeRepr)
2728
case KeywordAs(fromType: TypeRepr, toType: TypeRepr)
2829
case KeywordEach(fromType: TypeRepr, toType: TypeRepr, eachInstance: Term)
@@ -31,14 +32,14 @@ private[focus] trait FocusBase {
3132
case KeywordWithDefault(toType: TypeRepr, defaultValue: Term)
3233

3334
override def toString(): String = this match {
34-
case SelectField(fieldName, fromType, fromTypeArgs, toType) =>
35-
s"SelectField($fieldName, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ${toType.show})"
36-
case SelectFieldWithImplicits(fieldName, fromType, toType, setter) =>
37-
s"SelectFieldWithImplicits($fieldName, ${fromType.show}, ${toType.show}, ...)"
38-
case SelectOnlyField(fieldName, fromType, fromTypeArgs, _, toType) =>
39-
s"SelectOnlyField($fieldName, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ..., ${toType.show})"
40-
case SelectOnlyFieldWithImplicits(fieldName, fromType, toType, reverseGet) =>
41-
s"SelectOnlyFieldWithImplicits($fieldName, ${fromType.show}, ${toType.show}, ...)"
35+
case SelectField(caseFieldSymbol, fromType, fromTypeArgs, toType) =>
36+
s"SelectField(${caseFieldSymbol.name}, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ${toType.show})"
37+
case SelectFieldWithImplicits(caseFieldSymbol, fromType, toType, setter) =>
38+
s"SelectFieldWithImplicits(${caseFieldSymbol.name}, ${fromType.show}, ${toType.show}, ...)"
39+
case SelectOnlyField(caseFieldSymbol, fromType, fromTypeArgs, _, toType) =>
40+
s"SelectOnlyField(${caseFieldSymbol.name}, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ..., ${toType.show})"
41+
case SelectOnlyFieldWithImplicits(caseFieldSymbol, fromType, toType, reverseGet) =>
42+
s"SelectOnlyFieldWithImplicits(${caseFieldSymbol.name}, ${fromType.show}, ${toType.show}, ...)"
4243
case KeywordSome(toType) => s"KeywordSome(${toType.show})"
4344
case KeywordAs(fromType, toType) => s"KeywordAs(${fromType.show}, ${toType.show})"
4445
case KeywordEach(fromType, toType, _) => s"KeywordEach(${fromType.show}, ${toType.show}, ...)"
@@ -51,6 +52,7 @@ private[focus] trait FocusBase {
5152
enum FocusError {
5253
case NotACaseClass(className: String, fieldName: String)
5354
case NotACaseField(className: String, fieldName: String)
55+
case NonImplicitNonCaseParameter(className: String, parameters: List[String])
5456
case NotAConcreteClass(className: String)
5557
case DidNotDirectlyAccessArgument(argName: String)
5658
case NotASimpleLambdaFunction

core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala

+34-22
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,34 @@ private[focus] trait SelectParserBase extends ParserBase {
1111

1212
import this.macroContext.reflect._
1313

14-
// Match on a term that is an instance of a case class
15-
object CaseClass {
16-
def unapply(term: Term): Option[(Term, Symbol)] =
14+
case class CaseClass(typeRepr: TypeRepr, classSymbol: Symbol) {
15+
val typeArgs: List[TypeRepr] = getSuppliedTypeArgs(typeRepr)
16+
val companionObject: Term = Ref(classSymbol.companionModule)
17+
18+
private val (typeParams, caseFieldParams :: otherParams) =
19+
classSymbol.primaryConstructor.paramSymss.span(_.head.isTypeParam)
20+
val hasOnlyOneCaseField: Boolean = caseFieldParams.length == 1
21+
val hasOnlyOneParameterList: Boolean = otherParams.isEmpty
22+
private val nonCaseNonImplicitParameters: List[Symbol] =
23+
otherParams.flatten.filterNot(symbol => symbol.flags.is(Flags.Implicit) || symbol.flags.is(Flags.Given))
24+
val allOtherParametersAreImplicitResult: FocusResult[Unit] = nonCaseNonImplicitParameters match {
25+
case Nil => Right(())
26+
case list => FocusError.NonImplicitNonCaseParameter(typeRepr.show, list.map(_.name)).asResult
27+
}
28+
29+
def getCaseFieldSymbol(fieldName: String): FocusResult[Symbol] =
30+
classSymbol.caseFields.find(_.name == fieldName) match {
31+
case Some(symbol) => Right(symbol)
32+
case None => FocusError.NotACaseField(typeRepr.show, fieldName).asResult
33+
}
34+
def getCaseFieldType(caseFieldSymbol: Symbol): FocusResult[TypeRepr] =
35+
getFieldType(typeRepr, caseFieldSymbol)
36+
}
37+
38+
object CaseClassExtractor {
39+
def unapply(term: Term): Option[CaseClass] =
1740
term.tpe.classSymbol.flatMap { sym =>
18-
Option.when(sym.flags.is(Flags.Case))((term, sym))
41+
Option.when(sym.flags.is(Flags.Case))(CaseClass(getType(term), sym))
1942
}
2043
}
2144

@@ -25,32 +48,21 @@ private[focus] trait SelectParserBase extends ParserBase {
2548
case _ => Nil
2649
}
2750

28-
def getClassSymbol(tpe: TypeRepr): FocusResult[Symbol] = tpe.classSymbol match {
29-
case Some(sym) => Right(sym)
30-
case None => FocusError.NotAConcreteClass(tpe.show).asResult
31-
}
32-
33-
def getFieldType(fromType: TypeRepr, fieldName: String): FocusResult[TypeRepr] = {
34-
// We need to do this to support tuples, because even though they conform as case classes in other respects,
35-
// for some reason their field names (_1, _2, etc) have a space at the end, ie `_1 `.
36-
def getTrimmedFieldSymbol(fromTypeSymbol: Symbol): Symbol =
37-
fromTypeSymbol.fieldMembers.find(_.name.trim == fieldName).getOrElse(Symbol.noSymbol)
38-
39-
getClassSymbol(fromType).flatMap { fromTypeSymbol =>
40-
getTrimmedFieldSymbol(fromTypeSymbol) match {
41-
case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(fromType, possiblyTypeArg))
42-
case _ => FocusError.CouldntFindFieldType(fromType.show, fieldName).asResult
43-
}
51+
def getFieldType(fromType: TypeRepr, caseFieldSymbol: Symbol): FocusResult[TypeRepr] =
52+
caseFieldSymbol match {
53+
case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(fromType, possiblyTypeArg))
54+
case _ => FocusError.CouldntFindFieldType(fromType.show, caseFieldSymbol.name).asResult
4455
}
45-
}
4656

4757
private object FieldType {
4858
def unapply(fieldSymbol: Symbol): Option[TypeRepr] = fieldSymbol match {
4959
case sym if sym.isNoSymbol => None
5060
case sym =>
5161
sym.tree match {
5262
case ValDef(_, typeTree, _) => Some(typeTree.tpe)
53-
case _ => None
63+
// Only needed for Tuples because `_1` is a DefDef while `_1 ` is a ValDef.
64+
case DefDef(_, _, typeTree, _) => Some(typeTree.tpe)
65+
case _ => None
5466
}
5567
}
5668
}

core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldGenerator.scala

+11-11
Original file line numberDiff line numberDiff line change
@@ -9,61 +9,61 @@ private[focus] trait SelectFieldGenerator {
99

1010
import macroContext.reflect._
1111

12-
private def generateGetter(from: Term, fieldName: String): Term =
13-
Select.unique(from, fieldName) // o.field
12+
private def generateGetter(from: Term, caseFieldSymbol: Symbol): Term =
13+
Select(from, caseFieldSymbol) // o.field
1414

1515
def generateSelectField(action: FocusAction.SelectField): Term = {
16-
import action.{fieldName, fromType, fromTypeArgs, toType}
16+
import action.{caseFieldSymbol, fromType, fromTypeArgs, toType}
1717

1818
def generateSetter(from: Term, to: Term): Term =
19-
Select.overloaded(from, "copy", fromTypeArgs, NamedArg(fieldName, to) :: Nil) // o.copy(field = value)
19+
Select.overloaded(from, "copy", fromTypeArgs, NamedArg(caseFieldSymbol.name, to) :: Nil) // o.copy(field = value)
2020

2121
(fromType.asType, toType.asType) match {
2222
case ('[f], '[t]) =>
2323
'{
24-
Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })((to: t) =>
24+
Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).asExprOf[t] })((to: t) =>
2525
(from: f) => ${ generateSetter('{ from }.asTerm, '{ to }.asTerm).asExprOf[f] }
2626
)
2727
}.asTerm
2828
}
2929
}
3030

3131
def generateSelectFieldWithImplicits(action: FocusAction.SelectFieldWithImplicits): Term = {
32-
import action.{fieldName, fromType, toType, setter}
32+
import action.{caseFieldSymbol, fromType, toType, setter}
3333

3434
(fromType.asType, toType.asType) match {
3535
case ('[f], '[t]) =>
3636
'{
37-
Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })(
37+
Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).asExprOf[t] })(
3838
${ setter.asExprOf[t => f => f] }
3939
)
4040
}.asTerm
4141
}
4242
}
4343

4444
def generateSelectOnlyField(action: FocusAction.SelectOnlyField): Term = {
45-
import action.{fieldName, fromType, fromTypeArgs, fromCompanion, toType}
45+
import action.{caseFieldSymbol, fromType, fromTypeArgs, fromCompanion, toType}
4646

4747
def generateReverseGet(to: Term): Term =
4848
Select.overloaded(fromCompanion, "apply", fromTypeArgs, List(to)) // Companion.apply(value)
4949

5050
(fromType.asType, toType.asType) match {
5151
case ('[f], '[t]) =>
5252
'{
53-
Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })((to: t) =>
53+
Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).asExprOf[t] })((to: t) =>
5454
${ generateReverseGet('{ to }.asTerm).asExprOf[f] }
5555
)
5656
}.asTerm
5757
}
5858
}
5959

6060
def generateSelectOnlyFieldWithImplicits(action: FocusAction.SelectOnlyFieldWithImplicits): Term = {
61-
import action.{fieldName, fromType, toType, reverseGet}
61+
import action.{caseFieldSymbol, fromType, toType, reverseGet}
6262

6363
(fromType.asType, toType.asType) match {
6464
case ('[f], '[t]) =>
6565
'{
66-
Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })(
66+
Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).asExprOf[t] })(
6767
${ reverseGet.asExprOf[t => f] }
6868
)
6969
}.asTerm

core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldParser.scala

+59-57
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,19 @@ private[focus] trait SelectFieldParser {
1212

1313
def unapply(term: Term): Option[FocusResult[(RemainingCode, FocusAction)]] = term match {
1414

15-
case Select(CaseClass(remainingCode, classSymbol), fieldName) =>
16-
if (isCaseField(classSymbol, fieldName)) {
17-
val fromType = getType(remainingCode)
18-
val action = (hasOnlyOneParameterList(classSymbol), hasOnlyOneField(classSymbol)) match {
19-
case (true, false) => getSelectFieldAction(fromType, fieldName)
20-
case (false, false) => getSelectFieldActionWithImplicits(fromType, fieldName)
21-
case (true, true) => getSelectOnlyFieldAction(fromType, classSymbol, fieldName)
22-
case (false, true) => getSelectOnlyFieldActionWithImplicits(fromType, classSymbol, fieldName)
23-
}
24-
val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a))
25-
Some(remainingCodeWithAction)
26-
} else {
27-
Some(FocusError.NotACaseField(remainingCode.tpe.show, fieldName).asResult)
28-
}
15+
case Select(remainingCode @ CaseClassExtractor(caseClass: CaseClass), fieldName) =>
16+
Some(
17+
for {
18+
_ <- caseClass.allOtherParametersAreImplicitResult
19+
caseFieldSymbol <- caseClass.getCaseFieldSymbol(fieldName)
20+
action <- (caseClass.hasOnlyOneParameterList, caseClass.hasOnlyOneCaseField) match {
21+
case (true, false) => getSelectFieldAction(caseClass, caseFieldSymbol)
22+
case (false, false) => getSelectFieldActionWithImplicits(caseClass, caseFieldSymbol)
23+
case (true, true) => getSelectOnlyFieldAction(caseClass, caseFieldSymbol)
24+
case (false, true) => getSelectOnlyFieldActionWithImplicits(caseClass, caseFieldSymbol)
25+
}
26+
} yield (RemainingCode(remainingCode), action)
27+
)
2928

3029
case Select(remainingCode, fieldName) =>
3130
Some(FocusError.NotACaseClass(remainingCode.tpe.show, fieldName).asResult)
@@ -34,57 +33,60 @@ private[focus] trait SelectFieldParser {
3433
}
3534
}
3635

37-
private def isCaseField(classSymbol: Symbol, fieldName: String): Boolean =
38-
classSymbol.caseFields.exists(_.name == fieldName)
39-
40-
private def hasOnlyOneField(classSymbol: Symbol): Boolean =
41-
classSymbol.caseFields.length == 1
42-
43-
private def hasOnlyOneParameterList(classSymbol: Symbol): Boolean =
44-
classSymbol.primaryConstructor.paramSymss match {
45-
case _ :: Nil => true
46-
case (head :: _) :: _ :: Nil if head.isTypeParam => true
47-
case _ => false
48-
}
49-
50-
private def getCompanionObject(classSymbol: Symbol): Term =
51-
Ref(classSymbol.companionModule)
52-
53-
private def getSelectFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] =
54-
getFieldType(fromType, fieldName).flatMap { toType =>
55-
Right(FocusAction.SelectField(fieldName, fromType, getSuppliedTypeArgs(fromType), toType))
56-
}
57-
58-
private def getSelectFieldActionWithImplicits(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] =
59-
getFieldType(fromType, fieldName).flatMap { toType =>
60-
val typeArgs = getSuppliedTypeArgs(fromType)
61-
constructSetter(fieldName, fromType, toType, typeArgs).map { setter =>
62-
FocusAction.SelectFieldWithImplicits(fieldName, fromType, toType, setter)
63-
}
64-
}
36+
private def getSelectFieldAction(
37+
caseClass: CaseClass,
38+
caseFieldSymbol: Symbol
39+
): FocusResult[FocusAction] =
40+
for {
41+
toType <- caseClass.getCaseFieldType(caseFieldSymbol)
42+
} yield FocusAction.SelectField(
43+
caseFieldSymbol,
44+
caseClass.typeRepr,
45+
caseClass.typeArgs,
46+
toType
47+
)
48+
49+
private def getSelectFieldActionWithImplicits(
50+
caseClass: CaseClass,
51+
caseFieldSymbol: Symbol
52+
): FocusResult[FocusAction] =
53+
for {
54+
toType <- caseClass.getCaseFieldType(caseFieldSymbol)
55+
setter <- constructSetter(caseFieldSymbol.name, caseClass.typeRepr, toType, caseClass.typeArgs)
56+
} yield FocusAction.SelectFieldWithImplicits(
57+
caseFieldSymbol,
58+
caseClass.typeRepr,
59+
toType,
60+
setter
61+
)
6562

6663
private def getSelectOnlyFieldAction(
67-
fromType: TypeRepr,
68-
fromClassSymbol: Symbol,
69-
fieldName: String
64+
caseClass: CaseClass,
65+
caseFieldSymbol: Symbol
7066
): FocusResult[FocusAction] =
7167
for {
72-
toType <- getFieldType(fromType, fieldName)
73-
companion = getCompanionObject(fromClassSymbol)
74-
supplied = getSuppliedTypeArgs(fromType)
75-
} yield FocusAction.SelectOnlyField(fieldName, fromType, supplied, companion, toType)
68+
toType <- caseClass.getCaseFieldType(caseFieldSymbol)
69+
} yield FocusAction.SelectOnlyField(
70+
caseFieldSymbol,
71+
caseClass.typeRepr,
72+
caseClass.typeArgs,
73+
caseClass.companionObject,
74+
toType
75+
)
7676

7777
private def getSelectOnlyFieldActionWithImplicits(
78-
fromType: TypeRepr,
79-
fromClassSymbol: Symbol,
80-
fieldName: String
78+
caseClass: CaseClass,
79+
caseFieldSymbol: Symbol
8180
): FocusResult[FocusAction] =
8281
for {
83-
toType <- getFieldType(fromType, fieldName)
84-
companion = getCompanionObject(fromClassSymbol)
85-
supplied = getSuppliedTypeArgs(fromType)
86-
reverseGet <- constructReverseGet(companion, fromType, toType, supplied)
87-
} yield FocusAction.SelectOnlyFieldWithImplicits(fieldName, fromType, toType, reverseGet)
82+
toType <- caseClass.getCaseFieldType(caseFieldSymbol)
83+
reverseGet <- constructReverseGet(caseClass.companionObject, caseClass.typeRepr, toType, caseClass.typeArgs)
84+
} yield FocusAction.SelectOnlyFieldWithImplicits(
85+
caseFieldSymbol,
86+
caseClass.typeRepr,
87+
toType,
88+
reverseGet
89+
)
8890

8991
private case class LiftException(error: FocusError) extends Exception
9092

macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ class ContextBoundCompilationIssueSpec extends DisciplineSuite {
1313
val lens: Lens[S[T], Bar[T]] = GenLens[S[T]](_.bar)
1414
}
1515

16-
private case class S[T: Foo](bar: Bar[T])
16+
private case class S[T: Foo](bar: Bar[T]) {
17+
def bar(t: T): T = t
18+
}
1719

1820
private case object FooImpl extends Foo[Unit]
1921
private case object BarImpl extends Bar[Unit]

0 commit comments

Comments
 (0)