Skip to content

Conversation

@HT154
Copy link
Contributor

@HT154 HT154 commented Dec 12, 2025

No description provided.

@HT154 HT154 changed the title SPICE-0024: Annotation converters SPICE-0024: Annotation converters Dec 12, 2025
+
[source,pkl]
----
class SnakeCase extends Annotation {
Copy link
Contributor

Choose a reason for hiding this comment

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

You mentioned you didn't want an implicit ConverterAnnotation and I agree it's better to be explicit. But why not provide it just for usability sake? No one would be forced to use it, but I imagine many people would rather use a class pre-made for this use case than remember what the api is supposed to be like.
That would make it easier to remember what a converter is supposed to do, and the converter code would be:

annotationConverters {
  [SnakeCase] = (name, annotation, value) -> annotation.convert(name, value)
}

It could also have methods that only take the property name or just the value. So you can choose what to overwrite

abstract class ConverterAnnotation extends Annotation {
  function convert(propertyName: String, value: Any): Pair<String, Any> =
    Pair(convertName(propertyName), convertValue(value))

  function convertName(propertyName: String): String = propertyName

  function convertValue(value: Any): Any = value
}

You would still be able to write your own annotation and call it however you please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you proposing that renderers implicitly convert subclasses of ConverterAnnotation by default? Or would users still need to explicitly add the converter?

I would be opposed to adding an implicit annotation converter for ConverterAnnotation (for the reasons outlined in the SPICE), but open to adding this type for explicit use if y'all feel we should.
One reason to omit this is that it does increase the surface area of pkl:base for a type that is basically sugar.

Copy link
Contributor

Choose a reason for hiding this comment

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

Are you proposing that renderers implicitly convert subclasses of ConverterAnnotation by default?

No. It all explicit, as you proposed.
You still need to wire the converters in the renderer body.
This class would just be there as a template that you can (but don't have to) use, so you can:

class RenderDuration extends ConverterAnnotation {
  unit: DurationUnit

  function convertValue(value: Duration): Number = value.toUnit(unit).value
}

output {
  renderer {
    annotationConverters {
      [RenderDuration] = (prop, annotation, value) -> annotation.convert(prop, value)
    }
  }
}

Copy link
Member

Choose a reason for hiding this comment

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

Creating a new class in the base module is a breaking change to how name resolution works.
So, I'd rather be conservative and avoid adding new classes for this (unless we want to put this in another stdlib module).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, putting this on base is a bad idea. And we can always add it later.

@HT154 HT154 force-pushed the annotation-converters branch from 7e6d0c5 to 85a2131 Compare January 5, 2026 19:37
@HT154 HT154 requested review from bioball and stackoverflow January 5, 2026 19:38
@HT154 HT154 force-pushed the annotation-converters branch from 85a2131 to f1e03cd Compare January 5, 2026 19:50
}

class Bird {
@SnakeCase
Copy link
Member

Choose a reason for hiding this comment

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

At some point, it might be good to add some way to control what an annotation can be applied to.

This avoids mistakes like applying @SnakeCase to a class, typealias, etc.

Copy link
Member

@bioball bioball left a comment

Choose a reason for hiding this comment

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

Do we ever care to support annotation converters on something that's not a property?

Right now, this isn't meaningful, but it might be if we ever support annotations on other source code locations.

foo {
  @MyAnnotation
  new { bar =1 }
}

Thinking through this: I don't think it's likely that we'd support adding annotations to object members. So, my own conclusion here is that... no, we don't care for it.

abstract class BaseValueRenderer {
// ...

annotationConverters: Mapping<Class, (String, Annotation, unknown) -> Pair<String, Any>>
Copy link
Member

Choose a reason for hiding this comment

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

The order of parameters here seems strange. Why is Annotation in between the key and the value?

I think it should be:

Suggested change
annotationConverters: Mapping<Class, (String, Annotation, unknown) -> Pair<String, Any>>
annotationConverters: Mapping<Class, (Annotation, String, unknown) -> Pair<String, Any>>

This is a purely additive change.
Modules that adopt annotation converters will not be compatible with prior versions of Pkl.

== Related Topics
Copy link
Member

Choose a reason for hiding this comment

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

Something else to mention here is: annotation converters should eventually influence parsing, too.
This way, you can round-trip between Pkl and some external format.

The story is somewhat muddy right now because we don't have any way to parse into a Typed value.
But, once we have that, a Parser should also support annotation converters, where the converter goes the opposite direction.

@HT154 HT154 force-pushed the annotation-converters branch from f1e03cd to 43e489d Compare January 12, 2026 19:56
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.

3 participants