diff --git a/build.sbt b/build.sbt index 37f562856..08b09ef70 100644 --- a/build.sbt +++ b/build.sbt @@ -51,7 +51,12 @@ lazy val buildSettings = Seq( Compile / console / scalacOptions -= "-Ywarn-unused:imports", scalacOptions ++= { if (tlIsScala3.value) - Seq("-source:3.0-migration", "-Ykind-projector", "-language:implicitConversions,higherKinds,postfixOps") + Seq( + "-source:3.0-migration", + "-Ykind-projector", + "-language:implicitConversions,higherKinds,postfixOps", + "-Wunused:all" + ) else Seq( "-Ymacro-annotations", @@ -158,7 +163,9 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) ProblemFilters.exclude[MissingClassProblem]("monocle.syntax.AsPrism"), ProblemFilters.exclude[MissingClassProblem]("monocle.syntax.AsPrism$"), ProblemFilters.exclude[MissingClassProblem]("monocle.syntax.AsPrismImpl"), - ProblemFilters.exclude[MissingClassProblem]("monocle.syntax.AsPrismImpl$") + ProblemFilters.exclude[MissingClassProblem]("monocle.syntax.AsPrismImpl$"), + // ignore mima for classes only used by `Focus` macro + ProblemFilters.exclude[DirectMissingMethodProblem]("monocle.internal.focus.*") ) else Nil } diff --git a/core/shared/src/main/scala-3/monocle/internal/focus/ErrorHandling.scala b/core/shared/src/main/scala-3/monocle/internal/focus/ErrorHandling.scala index bd7eaec07..1dc59ce0d 100644 --- a/core/shared/src/main/scala-3/monocle/internal/focus/ErrorHandling.scala +++ b/core/shared/src/main/scala-3/monocle/internal/focus/ErrorHandling.scala @@ -3,19 +3,52 @@ package monocle.internal.focus private[focus] trait ErrorHandling { this: FocusBase => - def errorMessage(error: FocusError): String = error match { - case FocusError.NotACaseClass(fromClass, fieldName) => - s"Cannot generate Lens for field '$fieldName', because '$fromClass' is not a case class" + def errorReport(error: FocusError): (String, Option[Position]) = error match { + case FocusError.NotACaseClass(fromClass, fieldName, pos) => + ( + s"Cannot generate Lens for field '$fieldName', because '$fromClass' is not a case class", + Some(pos) + ) case FocusError.NotAConcreteClass(fromClass) => - s"Expecting a concrete case class in the 'From' position; cannot reify type $fromClass" + ( + s"Expecting a concrete case class in the 'From' position; cannot reify type $fromClass", + None + ) case FocusError.NotASimpleLambdaFunction => - s"Expecting a lambda function that directly accesses a field. Example: `Focus[Address](_.streetNumber)`" - case FocusError.CouldntUnderstandKeywordContext => s"Internal error in monocle.Focus; cannot access special syntax." + ( + s"Expecting a lambda function that directly accesses a field. Example: `Focus[Address](_.streetNumber)`", + None + ) + case FocusError.CouldntUnderstandKeywordContext => + ( + s"Internal error in monocle.Focus; cannot access special syntax.", + None + ) case FocusError.DidNotDirectlyAccessArgument(argName) => - s"Expecting a lambda function that directly accesses the argument; other variable `$argName` found. Example: `Focus[Address](_.streetNumber)`" - case FocusError.ComposeMismatch(type1, type2) => s"Could not compose $type1.andThen($type2)" - case FocusError.UnexpectedCodeStructure(code) => s"Unexpected code structure: $code" - case FocusError.CouldntFindFieldType(fromType, fieldName) => s"Couldn't find type for $fromType.$fieldName" - case FocusError.InvalidDowncast(fromType, toType) => s"Type '$fromType' could not be cast to '$toType'" + ( + s"Expecting a lambda function that directly accesses the argument; other variable `$argName` found. Example: `Focus[Address](_.streetNumber)`", + None + ) + case FocusError.ComposeMismatch(type1, type2) => + ( + s"Could not compose $type1.andThen($type2)", + None + ) + case FocusError.UnexpectedCodeStructure(code) => + ( + s"Unexpected code structure: $code", + None + ) + case FocusError.CouldntFindFieldType(fromType, fieldName, pos) => + ( + s"Couldn't find type for $fromType.$fieldName", + Some(pos) + ) + case FocusError.InvalidDowncast(fromType, toType) => + ( + s"Type '$fromType' could not be cast to '$toType'", + None + ) } + } diff --git a/core/shared/src/main/scala-3/monocle/internal/focus/FocusBase.scala b/core/shared/src/main/scala-3/monocle/internal/focus/FocusBase.scala index 0917606c9..8a68bbaca 100644 --- a/core/shared/src/main/scala-3/monocle/internal/focus/FocusBase.scala +++ b/core/shared/src/main/scala-3/monocle/internal/focus/FocusBase.scala @@ -9,6 +9,7 @@ private[focus] trait FocusBase { type Term = macroContext.reflect.Term type TypeRepr = macroContext.reflect.TypeRepr + type Position = macroContext.reflect.Position case class LambdaConfig(argName: String, lambdaBody: Term) @@ -43,13 +44,13 @@ private[focus] trait FocusBase { } enum FocusError { - case NotACaseClass(className: String, fieldName: String) + case NotACaseClass(className: String, fieldName: String, pos: Position) case NotAConcreteClass(className: String) case DidNotDirectlyAccessArgument(argName: String) case NotASimpleLambdaFunction case CouldntUnderstandKeywordContext case UnexpectedCodeStructure(code: String) - case CouldntFindFieldType(fromType: String, fieldName: String) + case CouldntFindFieldType(fromType: String, fieldName: String, pos: Position) case ComposeMismatch(type1: String, type2: String) case InvalidDowncast(fromType: String, toType: String) diff --git a/core/shared/src/main/scala-3/monocle/internal/focus/FocusImpl.scala b/core/shared/src/main/scala-3/monocle/internal/focus/FocusImpl.scala index 637f29c82..7ab7bc40e 100644 --- a/core/shared/src/main/scala-3/monocle/internal/focus/FocusImpl.scala +++ b/core/shared/src/main/scala-3/monocle/internal/focus/FocusImpl.scala @@ -25,7 +25,9 @@ private[focus] class FocusImpl(val macroContext: Quotes) generatedCode match { case Right(code) => code.asExpr - case Left(error) => report.error(errorMessage(error)); '{ ??? } + case Left(error) => + val (msg, pos) = errorReport(error) + report.errorAndAbort(msg, pos.getOrElse(lambda.asTerm.pos)) } } } diff --git a/core/shared/src/main/scala-3/monocle/internal/focus/features/SelectParserBase.scala b/core/shared/src/main/scala-3/monocle/internal/focus/features/SelectParserBase.scala index 3df1ffa99..85a5e527f 100644 --- a/core/shared/src/main/scala-3/monocle/internal/focus/features/SelectParserBase.scala +++ b/core/shared/src/main/scala-3/monocle/internal/focus/features/SelectParserBase.scala @@ -26,16 +26,24 @@ private[focus] trait SelectParserBase extends ParserBase { case None => FocusError.NotAConcreteClass(tpe.show).asResult } - def getFieldType(fromType: TypeRepr, fieldName: String): FocusResult[TypeRepr] = { - // We need to do this to support tuples, because even though they conform as case classes in other respects, - // for some reason their field names (_1, _2, etc) have a space at the end, ie `_1 `. - def getTrimmedFieldSymbol(fromTypeSymbol: Symbol): Symbol = - fromTypeSymbol.memberFields.find(_.name.trim == fieldName).getOrElse(Symbol.noSymbol) + private val tupleFieldPattern = "^_[0-9]+$".r + + def getFieldType(fromType: TypeRepr, fieldName: String, pos: Position): FocusResult[TypeRepr] = { + def getFieldSymbol(fromTypeSymbol: Symbol): Symbol = { + // We need to do this to support tuples, because even though they conform as case classes in other respects, + // for some reason their field names (_1, _2, etc) have a space at the end, ie `_1 `. + val f: String => String = + if (fromType <:< TypeRepr.of[Tuple] && tupleFieldPattern.matches(fieldName)) + _.trim + else + identity + fromTypeSymbol.fieldMembers.find(s => f(s.name) == fieldName).getOrElse(Symbol.noSymbol) + } getClassSymbol(fromType).flatMap { fromTypeSymbol => - getTrimmedFieldSymbol(fromTypeSymbol) match { + getFieldSymbol(fromTypeSymbol) match { case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(fromType, possiblyTypeArg)) - case _ => FocusError.CouldntFindFieldType(fromType.show, fieldName).asResult + case _ => FocusError.CouldntFindFieldType(fromType.show, fieldName, pos).asResult } } } diff --git a/core/shared/src/main/scala-3/monocle/internal/focus/features/selectfield/SelectFieldParser.scala b/core/shared/src/main/scala-3/monocle/internal/focus/features/selectfield/SelectFieldParser.scala index a87a76873..84d3bcf99 100644 --- a/core/shared/src/main/scala-3/monocle/internal/focus/features/selectfield/SelectFieldParser.scala +++ b/core/shared/src/main/scala-3/monocle/internal/focus/features/selectfield/SelectFieldParser.scala @@ -14,19 +14,18 @@ private[focus] trait SelectFieldParser { case Select(CaseClass(remainingCode), fieldName) => val fromType = getType(remainingCode) - val action = getFieldAction(fromType, fieldName) + val action = getFieldAction(fromType, fieldName, term.pos) val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) Some(remainingCodeWithAction) case Select(remainingCode, fieldName) => - Some(FocusError.NotACaseClass(remainingCode.tpe.show, fieldName).asResult) - + Some(FocusError.NotACaseClass(remainingCode.tpe.widen.show, fieldName, term.pos).asResult) case _ => None } } - private def getFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = - getFieldType(fromType, fieldName).flatMap { toType => + private def getFieldAction(fromType: TypeRepr, fieldName: String, pos: Position): FocusResult[FocusAction] = + getFieldType(fromType, fieldName, pos).flatMap { toType => Right(FocusAction.SelectField(fieldName, fromType, getSuppliedTypeArgs(fromType), toType)) } } diff --git a/core/shared/src/main/scala-3/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala b/core/shared/src/main/scala-3/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala index 17cb11036..9ff1de75f 100644 --- a/core/shared/src/main/scala-3/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala +++ b/core/shared/src/main/scala-3/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala @@ -14,7 +14,7 @@ private[focus] trait SelectOnlyFieldParser { case Select(CaseClass(remainingCode), fieldName) if hasOnlyOneField(remainingCode) => val fromType = getType(remainingCode) - val action = getFieldAction(fromType, fieldName) + val action = getFieldAction(fromType, fieldName, term.pos) val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) Some(remainingCodeWithAction) @@ -22,9 +22,9 @@ private[focus] trait SelectOnlyFieldParser { } } - private def getFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = + private def getFieldAction(fromType: TypeRepr, fieldName: String, pos: Position): FocusResult[FocusAction] = for { - toType <- getFieldType(fromType, fieldName) + toType <- getFieldType(fromType, fieldName, pos) companion <- getCompanionObject(fromType) supplied = getSuppliedTypeArgs(fromType) } yield FocusAction.SelectOnlyField(fieldName, fromType, supplied, companion, toType)