Skip to content
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

feat: scala 3 derivation #8

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

i10416
Copy link

@i10416 i10416 commented Dec 14, 2024

close #5
This PR adds Scala 3 derivation.

It pass all the test except one which uses Map, that does not seem to have K0.ProductInstances.

See core/src/test/scala-3/toml/CodecSpec.scala.

@i10416 i10416 changed the title feat: scala 3 derivation baseline feat: scala 3 derivation Dec 14, 2024
@@ -9,12 +9,12 @@ trait Codec[A] {
defaults: Codec.Defaults,
index: Int
): Either[Parse.Error, A]
private[toml] def optional: Boolean = false
Copy link
Author

@i10416 i10416 Dec 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field makes it easier to handle default value for missing field of type Option[?]. toml does not have the concept of null or nil, so we cannot simply convert null into null-value-like AST node.

case None =>
Right(
d.defaultParams.get(witnessName)
.map(_.asInstanceOf[t])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultParams is Map[Strng, Any], so you need asInstanceOf

.left.map((a,m) => (witnessName +: a, m))
case None =>
d.defaultParams.get(witnessName) match
case None if codec.optional => Right(None.asInstanceOf[t])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional returns true if and only if codec is of type Codec[Option[?]], so it is safe to use None.asInstanceOf[t] where t is of type Option[?].

case Right(t) => ((Value.Tbl(map - head),(tail,0),(path,e)), Some(t))
case Left((path,e)) => ((value, (labels,index), (head :: path, e)), None)
case Value.Tbl(map) =>
map.keySet.diff(labelling.elemLabels.toSet).headOption match
Copy link
Author

@i10416 i10416 Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scala 2 derivation does not allow redundant fields for type, so I follow that rule.

): DerivedProductCodec[P] with
def apply(value: Value, defaults: Defaults, index: Int): Either[Parse.Error, P] =
type Acc = (Value, (/*labels: */Seq[String], Index), Parse.Error)
inst.unfold[Acc]((value, (labelling.elemLabels, index), (Nil, "")))(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It uses unfold instead of constructA because Scala 2 derivation performs stateful operation on labels and index.

d.defaultParams.get(head) match
case None =>
if codec.optional then
((value,(tail, index),(path,e)), Some(None.asInstanceOf[t]))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use None as default value for field of type Option[t].

@i10416
Copy link
Author

i10416 commented Dec 17, 2024

@keynmol Hello, I guess you're the maintainer of this project. Would you mind if I ask you to review this PR?

@keynmol
Copy link

keynmol commented Dec 26, 2024

Hi @i10416!

Sorry for slow response on this, blame holiday season.

I will look at this PR in the next couple of weeks, thanks for taking a stab at this!

I have my own version somewhere as well, so I'll compare issues and see how they can be merged to achieve perfection.

@i10416
Copy link
Author

i10416 commented Dec 26, 2024

I see.
The tricky parts, I think, are Option and Map[String, T]. I'm looking forward to your version so that I can get inspiration from it.

Have a nice holiday season!

Copy link

@keynmol keynmol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, this works, good job!

I added a few comments:

  1. one method needs to be extensively refactored, very difficult to follow
  2. as many things as possible should be made private[toml] (macros often prevent hiding some details, but hopefully various traits can be hidden here)
  3. the project doesn't yet have a .scalafmt.conf, to avoid reformatting everything at once (I will do it after your PR), would you mind formatting just the larger files you added in the PR using this config?
version = "3.8.3"
runner.dialect = scala213source3

fileOverride {
  "glob:**.sbt" {
    runner.dialect = scala212source3
  }

  "glob:**/project/**.*" {
    runner.dialect = scala212source3
  }

  "glob:**/scala-3/**/*.scala" {
    runner.dialect = scala3
    rewrite.scala3.insertEndMarkerMinLines = 10
    rewrite.scala3.removeOptionalBraces = true
    rewrite.scala3.convertToNewSyntax = true
  }
}

This will be the config that I will use for the rest of the project

using inst: K0.ProductInstances[Codec, P],labelling: Labelling[P], d:DefaultParams[P]
): DerivedProductCodec[P] with
def apply(value: Value, defaults: Defaults, index: Int): Either[Parse.Error, P] =
type Acc = (Value, (/*labels: */Seq[String], Index), Parse.Error)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using a tuple here makes things more confusing than they have to be – instead, let's use a case class with meaningful field names and refer to them directly, instead of destructuring with @unchecked.

At the moment the logic is very different to review because of the amount of tuples

package derivation
import shapeless3.deriving.*

trait DerivedSyntax {self: Codec.type =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be made private[toml]

private[toml] object macros:
inline def defaultParams[T]: Map[String, Any] = ${ defaultParmasImpl[T] }

def defaultParmasImpl[T](using quotes: Quotes, tpe: Type[T]): Expr[Map[String, Any]] =
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo - Parmas instead of Params

seems like the typo is copied from https://github.com/lampepfl/dotty-macro-examples/blob/main/defaultParamsInference/src/macro.scala

package toml
package derivation

trait DerivedSyntax {self: Codec.type => }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be made private[toml]


def parse(toml: String, extensions: Set[Extension] = Set()): Either[Parse.Error, Value.Tbl]

trait TomlVersionSpecific { self: Toml.type =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be this can be made private[toml]?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Scala 3 derivation
2 participants