Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.arainko.ducktape.internal

import io.github.arainko.ducktape.internal.Flag.Linter.Reason

import scala.quoted.*
import scala.quoted.runtime.StopMacroExpansion

Expand All @@ -9,22 +11,18 @@ private[ducktape] object Backend {
Context.Of[F]
)(
plan: Plan[Erroneous, F],
configs: List[Configuration.Instruction[F]]
configs: List[Configuration.Instruction[F]],
lintedFlags: Flag.Lints
)(using Quotes) = {
import quotes.reflect.*

val reconfiguredPlan = plan.configureAll(configs)

Logger.info("Original plan", plan)
Logger.info("Config", configs)
Logger.info("Reconfigured plan", reconfiguredPlan)

reconfiguredPlan.warnings
.groupBy(_.span)
.foreach { (span, warnings) =>
val messages = ConfigWarning.renderAll(warnings)
messages.foreach(report.warning(_, span.toPosition))
}
reportConfigWarnings(reconfiguredPlan)
reportFlagWarnings(lintedFlags)

reconfiguredPlan.result.refine match {
case Left(errors) =>
Expand Down Expand Up @@ -56,7 +54,7 @@ private[ducktape] object Backend {
case span: Span => span
}
.transform((_, errors) => errors.map(_.render).toList.distinct.mkString(System.lineSeparator))
.foreach { (span, errorMessafe) => quotes.reflect.report.error(errorMessafe, span.toPosition) }
.foreach { (span, errorMessage) => quotes.reflect.report.error(errorMessage, span.toPosition) }

throw new StopMacroExpansion
}
Expand Down Expand Up @@ -84,4 +82,26 @@ private[ducktape] object Backend {

String.join(System.lineSeparator, (renderSingle(self) :: suppressedErrors)*)
}

private def reportConfigWarnings(reconfiguredPlan: Plan.Reconfigured[?])(using Quotes) =
reconfiguredPlan.warnings
.groupBy(_.span)
.foreach { (span, warnings) =>
val messages = ConfigWarning.renderAll(warnings)
messages.foreach(quotes.reflect.report.warning(_, span.toPosition))
}

private def reportFlagWarnings(lintedFlags: Flag.Lints)(using Quotes) = {
lintedFlags.foreach { (flagSpan, reason) =>
val message = reason match {
case Reason.Unused =>
"Config is not actually being used anywhere"
case Reason.Overridden(overridder) =>
val pos = overridder.toPosition
val codeAndLocation = s"${pos.sourceCode.mkString} @ ${pos.sourceFile.name}:${pos.endLine + 1}:${pos.endColumn + 1}"
s"Config is being overridden by $codeAndLocation"
}
quotes.reflect.report.warning(message, flagSpan.toPosition)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,9 @@ private[ducktape] object ConfigParser {
private object RegionalConfig {
def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, Path)] = {
import quotes.reflect.*
PartialFunction.condOpt(term) {
term match {
// handle configs annotated with `: @nowarn` - Typed(term, _: Annotated) doesn't work for reasons unknown so we gotta do a wildcard...
case Typed(term, _) => unapply(term)
case Apply(
TypeApply(
Apply(
Expand All @@ -542,15 +544,18 @@ private[ducktape] object ConfigParser {
),
PathSelector(path) :: Nil
) =>
term -> path
Some(term -> path)
case _ => None
}
}
}

private object LocalConfig {
def unapply(using Quotes)(term: quotes.reflect.Term): Option[(quotes.reflect.Term, Path)] = {
import quotes.reflect.*
PartialFunction.condOpt(term) {
term match {
// handle configs annotated with `: @nowarn` - Typed(term, _: Annotated) doesn't work for reasons unknown so we gotta do a wildcard...
case Typed(term, _) => unapply(term)
case Apply(
TypeApply(
Apply(
Expand All @@ -561,7 +566,8 @@ private[ducktape] object ConfigParser {
),
PathSelector(path) :: Nil
) =>
term -> path
Some(term -> path)
case _ => None
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import scala.quoted.Quotes

private[ducktape] final case class ConfigWarning(span: Span, overriderSpan: Span, path: Path) {
def render(using Quotes): String = {
val pos = overriderSpan.withEnd(_ - 1).toPosition
val pos = overriderSpan.toPosition
val codeAndLocation = s"${pos.sourceCode.mkString} @ ${pos.sourceFile.name}:${pos.endLine + 1}:${pos.endColumn + 1}"

s"Config for ${path.render} is being overriden by $codeAndLocation"
s"Config for ${path.render} is being overridden by $codeAndLocation"
}
}

Expand All @@ -16,12 +16,12 @@ private[ducktape] object ConfigWarning {
warnings
.groupBy(_.overriderSpan)
.map { (overriderSpan, warnings) =>
val pos = overriderSpan.withEnd(_ - 1).toPosition
val pos = overriderSpan.toPosition
val codeAndLocation = s"${pos.sourceCode.mkString} @ ${pos.sourceFile.name}:${pos.endLine + 1}:${pos.endColumn + 1}"

if warnings.size > 1 then s"""Configs for:
|${warnings.map(warning => " * " + warning.path.render).mkString(System.lineSeparator)}
|are being overriden by $codeAndLocation""".stripMargin
|are being overridden by $codeAndLocation""".stripMargin
else warnings.map(_.render).mkString
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ private[ducktape] object FallibleTransformations {
val sourceStruct = Structure.of[A](Path.empty(Type.of[A]))
val destStruct = Structure.of[B](Path.empty(Type.of[B]))
val (config, flags) = Configuration.parse(configs, ConfigParser.fallible[F])
val plan = Planner.between(sourceStruct, destStruct, flags)
val (plan, lintedFlags) = Planner.between(sourceStruct, destStruct, flags)

val totalPlan = Backend.refineOrReportErrorsAndAbort(plan, config)
val totalPlan = Backend.refineOrReportErrorsAndAbort(plan, config, lintedFlags)
FalliblePlanInterpreter.run[F, A, B](totalPlan, source, Context.current.mode).asExprOf[F[B]]
}

Expand Down Expand Up @@ -75,8 +75,8 @@ private[ducktape] object FallibleTransformations {
}
.match {
case Left(error) => Backend.reportErrorsAndAbort(NonEmptyList(error), config)
case Right(plan) =>
val totalPlan = Backend.refineOrReportErrorsAndAbort(plan, config)
case Right(plan -> lintedFlags) =>
val totalPlan = Backend.refineOrReportErrorsAndAbort(plan, config, lintedFlags)
FalliblePlanInterpreter.run[F, A, B](totalPlan, value, Context.current.mode).asExprOf[F[B]]
}
}
Expand Down Expand Up @@ -116,10 +116,10 @@ private[ducktape] object FallibleTransformations {
}
.match {
case Left(error) => Backend.reportErrorsAndAbort(NonEmptyList(error), Nil)
case Right(plan) =>
case Right(plan -> lintedFlags) =>
plan.dest.tpe match {
case '[dest] =>
val totalPlan = Backend.refineOrReportErrorsAndAbort(plan, Nil)
val totalPlan = Backend.refineOrReportErrorsAndAbort(plan, Nil, lintedFlags)
FalliblePlanInterpreter.run[F, A, dest](totalPlan, value, Context.current.mode).asExprOf[F[dest]]
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.github.arainko.ducktape.internal

import scala.annotation.nowarn

private[ducktape] opaque type NonEmptyList[+A] = ::[A]

private[ducktape] object NonEmptyList {
Expand All @@ -27,7 +25,6 @@ private[ducktape] object NonEmptyList {

private[ducktape] def ::(elem: A): NonEmptyList[A] = Cons(elem, self)

@nowarn
private[ducktape] def :::(that: List[A]): NonEmptyList[A] = unsafeCoerce(toList ::: that)

private[ducktape] def map[B](f: A => B): NonEmptyList[B] = unsafeCoerce(toList.map(f))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@ import scala.util.boundary
private[ducktape] object Planner {
import Structure.*

def between[F <: Fallible](source: Structure, dest: Structure, flags: PlanFlags)(using Quotes, Context.Of[F]) = {
def between[F <: Fallible](source: Structure, dest: Structure, flags: PlanFlags)(using
Quotes,
Context.Of[F]
): (Plan[Erroneous, F], Flag.Lints) = {
given Depth = Depth.zero
given PlanFlags = flags
recurse(source, dest)
given linter: Flag.Linter = Flag.Linter.create(flags)
val plan = recurse(source, dest)
plan -> linter.lintedFlags
}

private def recurse[F <: Fallible](
source: Structure,
dest: Structure,
// TODO: Come up with something nicer
noUpcast: FallthroughUpcast = FallthroughUpcast.No
)(using quotes: Quotes, depth: Depth, context: Context.Of[F], flags: PlanFlags): Plan[Erroneous, F] = {
)(using quotes: Quotes, depth: Depth, context: Context.Of[F], flags: PlanFlags, linter: Flag.Linter): Plan[Erroneous, F] = {
import quotes.reflect.*
given Depth = depth.incremented
Logger.info("Flags going in: ", PlanFlags.current)
Expand Down Expand Up @@ -188,7 +193,7 @@ private[ducktape] object Planner {
private def planProductTransformation[F <: Fallible](
source: Structure.Product,
dest: Structure.Product
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val fieldPlans = dest.fields.map { (destField, destFieldStruct) =>
val plan =
source.fields
Expand Down Expand Up @@ -219,7 +224,7 @@ private[ducktape] object Planner {
private def planTupleTransformation[F <: Fallible](
source: Structure.Tuple,
dest: Structure.Tuple
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val plans = dest.elements.zipWithIndex.map { (destFieldStruct, index) =>
source.elements
.andThen(sourceStruct =>
Expand All @@ -245,7 +250,7 @@ private[ducktape] object Planner {
private def planTupleProductTransformation[F <: Fallible](
source: Structure.Tuple,
dest: Structure.Product
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val plans = dest.fields.zipWithIndex.map {
case (fieldName -> destFieldStruct, index) =>
val plan = source.elements
Expand Down Expand Up @@ -273,7 +278,7 @@ private[ducktape] object Planner {
private def planTupleFunctionTransformation[F <: Fallible](
source: Structure.Tuple,
dest: Structure.Function
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val plans = dest.args.zipWithIndex.map {
case (fieldName -> destFieldStruct, index) =>
val plan = source.elements
Expand Down Expand Up @@ -302,7 +307,7 @@ private[ducktape] object Planner {
private def planProductTupleTransformation[F <: Fallible](
source: Structure.Product,
dest: Structure.Tuple
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val sourceFields = source.fields.toVector
val plans = dest.elements.zipWithIndex.map { (destFieldStruct, index) =>
sourceFields
Expand Down Expand Up @@ -331,7 +336,7 @@ private[ducktape] object Planner {
private def planProductFunctionTransformation[F <: Fallible](
source: Structure.Product,
dest: Structure.Function
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val argPlans = dest.args.map { (destField, destFieldStruct) =>
val plan = source.fields
.andThen(sourceStruct =>
Expand Down Expand Up @@ -359,7 +364,7 @@ private[ducktape] object Planner {
private def planCoproductTransformation[F <: Fallible](
source: Structure.Coproduct,
dest: Structure.Coproduct
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val casePlans = source.children.map { (sourceName, sourceCaseStruct) =>
dest.children
.andThen(destCaseStruct =>
Expand Down Expand Up @@ -438,7 +443,7 @@ private[ducktape] object Planner {
object BetweenFallibles {
def unapply[F <: Fallible](
structs: (Structure, Structure)
)(using Quotes, Depth, Context.Of[F], PlanFlags): Option[Plan[Erroneous, F]] =
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter): Option[Plan[Erroneous, F]] =
PartialFunction.condOpt(Context.current *: structs) {
case (
ctx @ Context.PossiblyFallible(_, _, _, mode: TransformationMode.FailFast[f]),
Expand Down Expand Up @@ -507,9 +512,9 @@ private[ducktape] object Planner {
dest: Structure.Product,
sourceFlag: Option[Flag.Typed[Flag.Effect.FieldRename]],
destFlag: Option[Flag.Typed[Flag.Effect.FieldRename]]
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
val transformDestName = destFlag.map(_.effect.renamer).getOrElse(identity[String])
val transformSrcName = sourceFlag.map(_.effect.renamer).getOrElse(identity[String])
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
val transformDestName = destFlag.map(_.use).getOrElse(identity[String])
val transformSrcName = sourceFlag.map(_.use).getOrElse(identity[String])

// keys to transformed keys
val destAmbiguities = dest.fields.keys.groupBy(transformDestName).filter((_, ambs) => ambs.size > 1)
Expand Down Expand Up @@ -574,11 +579,11 @@ private[ducktape] object Planner {
dest: Structure.Coproduct,
sourceFlag: Option[Flag.Typed[Flag.Effect.CaseRename]],
destFlag: Option[Flag.Typed[Flag.Effect.CaseRename]]
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {
Logger.info("Flags going in: ", PlanFlags.current)

val transformDestName = destFlag.map(_.effect.renamer).getOrElse(identity[String])
val transformSrcName = sourceFlag.map(_.effect.renamer).getOrElse(identity[String])
val transformDestName = destFlag.map(_.use).getOrElse(identity[String])
val transformSrcName = sourceFlag.map(_.use).getOrElse(identity[String])

// keys to transformed keys
val destAmbiguities = dest.children.keys.toVector.groupBy(transformDestName).filter((_, ambs) => ambs.size > 1)
Expand Down Expand Up @@ -638,10 +643,10 @@ private[ducktape] object Planner {
dest: Structure.Function,
sourceFlag: Option[Flag.Typed[Flag.Effect.FieldRename]],
destFlag: Option[Flag.Typed[Flag.Effect.FieldRename]]
)(using Quotes, Depth, Context.Of[F], PlanFlags) = {
)(using Quotes, Depth, Context.Of[F], PlanFlags, Flag.Linter) = {

val transformDestName = destFlag.map(_.effect.renamer).getOrElse(identity[String])
val transformSrcName = sourceFlag.map(_.effect.renamer).getOrElse(identity[String])
val transformDestName = destFlag.map(_.use).getOrElse(identity[String])
val transformSrcName = sourceFlag.map(_.use).getOrElse(identity[String])

// keys to transformed keys
val destAmbiguities = dest.args.keys.groupBy(transformDestName).filter((_, ambs) => ambs.size > 1)
Expand Down Expand Up @@ -705,11 +710,11 @@ private[ducktape] object Planner {
case Yes, No
}

private def namesAreTheSame(source: Structure.Singleton, dest: Structure.Singleton)(using PlanFlags, Quotes) = {
private def namesAreTheSame(source: Structure.Singleton, dest: Structure.Singleton)(using PlanFlags, Flag.Linter, Quotes) = {
val sourceName =
PlanFlags.current.source.get[Flag.Effect.CaseRename](source.tpe).fold(identity[String])(_.effect.renamer)(source.name)
PlanFlags.current.source.get[Flag.Effect.CaseRename](source.tpe).fold(identity[String])(_.use)(source.name)
val destName =
PlanFlags.current.dest.get[Flag.Effect.CaseRename](dest.tpe).fold(identity[String])(_.effect.renamer)(dest.name)
PlanFlags.current.dest.get[Flag.Effect.CaseRename](dest.tpe).fold(identity[String])(_.use)(dest.name)

sourceName == destName
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.github.arainko.ducktape.internal

private[ducktape] opaque type SortedVector[+A] <: Vector[A] = Vector[A]

private[ducktape] object SortedVector {
def from[A: Ordering](vec: Vector[A]): SortedVector[A] = vec.sorted
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ private[ducktape] final case class Span(start: Int, end: Int) derives Debug {
import quotes.reflect.*
Position(SourceFile.current, start, end)
}

def withEnd(f: Int => Int): Span = copy(end = f(end))
}

private[ducktape] object Span {
Expand Down
Loading