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
Expand Up @@ -41,7 +41,7 @@ object Field {
): BuilderConfig[Source, Dest] = throw NotQuotedException("Field.renamed")

@compileTimeOnly("'Field.default' needs to be erased from the AST with a macro.")
def default[Source, Dest, FieldType](selector: Dest => FieldType)(using
def default[Source, Dest, FieldType](selector: Dest => FieldType)(using
@implicitNotFound("Field.default is supported for product types only, but ${Source} is not a product type.")
ev1: Mirror.ProductOf[Source],
@implicitNotFound("Field.default is supported for product types only, but ${Dest} is not a product type.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,17 @@ private[ducktape] object Failure {
override final def render(using Quotes): String = s"No field named '$fieldName' found in ${sourceType.show}"
}

final case class DefaultMissing(fieldName: String, destType: Type[?]) extends Failure {
final case class DefaultMissing(fieldName: String, destType: Type[?], config: Expr[Any]) extends Failure {
override def position(using Quotes): quotes.reflect.Position = config.pos

override final def render(using Quotes): String = s"No default value for '$fieldName' found in ${destType.show}"
}

final case class InvalidDefaultType(defaultField: Field, destType: Type[?]) extends Failure {
override final def render(using Quotes): String = s"The default value of '${destType.show}.${defaultField.name}' is not a subtype of ${defaultField.tpe.show}"
final case class InvalidDefaultType(defaultField: Field, destType: Type[?], config: Expr[Any]) extends Failure {
override def position(using Quotes): quotes.reflect.Position = config.pos

override final def render(using Quotes): String =
s"The default value of '${destType.show}.${defaultField.name}' is not a subtype of ${defaultField.tpe.show}"
}

final case class NoChildMapping(childName: String, destinationType: Type[?]) extends Failure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,16 @@ private[ducktape] object MaterializedConfiguration {
val destFieldName = Selectors.fieldName(Fields.dest, destSelector)
val sourceFieldName = Selectors.fieldName(Fields.source, sourceSelector)
Product.Renamed(destFieldName, sourceFieldName)(Pos.fromExpr(config)) :: Nil

case '{ FieldConfig.default[source, dest, destFieldType]($destSelector)(using $ev1, $ev2) } =>
val destFieldName = Selectors.fieldName(Fields.dest, destSelector)
val field = Fields.dest.unsafeGet(destFieldName)
val default = field.default.getOrElse(Failure.emit(Failure.DefaultMissing(field.name, Type.of[dest])))
val default = field.default.getOrElse(Failure.emit(Failure.DefaultMissing(field.name, Type.of[dest], config)))

Failure.cond(
successCondition = default.asTerm.tpe <:< TypeRepr.of(using field.tpe),
value = Product.Const(field.name, default)(Pos.fromExpr(config)) :: Nil,
failure = Failure.InvalidDefaultType(field, Type.of[dest])
failure = Failure.InvalidDefaultType(field, Type.of[dest], config)
)

case config @ '{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,41 @@ class Issue38Spec extends DucktapeSuite {
List(
testClass
.into[TestClassWithAdditionalGenericArg[String]]
.transform(
Field.default(_.additionalArg)
),
.transform(Field.default(_.additionalArg)),
Transformer
.define[TestClass, TestClassWithAdditionalGenericArg[String]]
.build(
Field.default(_.additionalArg)
)
.build(Field.default(_.additionalArg))
.transform(testClass)
)

actual.foreach(actual => assertEquals(actual, expected))
}

test("Field.default fails when a field doesn't have a default value") {
val testClass = TestClass("str", 1)

assertFailsToCompileWith {
"""
testClass
.into[TestClassWithAdditionalGenericArg[String]]
.transform(
Field.default(_.int)
)
"""
}("No default value for 'int' found in TestClassWithAdditionalGenericArg[String]")
}

test("Field.default fails when the default doesn't match the expected type") {
val testClass = TestClass("str", 1)

assertFailsToCompileWith {
"""
testClass
.into[TestClassWithAdditionalGenericArg[Int]]
.transform(
Field.default(_.additionalArg)
)
"""
}("The default value of 'TestClassWithAdditionalGenericArg[Int].additionalArg' is not a subtype of Int")
}
}