So I've seen there is some xml based option to dec...
# language-proposals
m
So I've seen there is some xml based option to declare external annotations for IDE, but I was surprised I have not found this to be an official thing. I'm still not sure how it works already so I'll ask here first - how about embedding such mechanism into core language? I.e. to be able to 'add' annotations for declarations from external modules that are then considered by the compiler when compiling this module. Use cases: • `@Nullable`/ `@NonNull`for java declarations. • `@Stable`/ `@Immutable`for Jetpack Compose - see this thread • `@Deprecated`for when you want to prevent using some symbol from external library Of course not all annotations would make sense to be used that way (think
@Volatile
) so there could be a meta annotation `@External`that only enables that annotations to be used externally.
e
m
So apparently it is not officially considered atm, I filled the proposal then https://youtrack.jetbrains.com/issue/KT-53496/Language-integrated-external-annotations
e
I can't imagine external annotations working for
@Serializable
- it would have to modify the original classes
@Stable
and
@Immutable
too - the presence of the annotation isn't the important part, it's what the compiler plugin does with the annotation during compilation of that class that matters. adding the annotation afterwards does nothing
the compose cases, maybe the compose plugin could have its own way of handling. but not sure how you foresee the serializer working… it would have to generate a new serializer class, which would conflict if created in multiple modules
m
For Compose - no, the important part is on the use side of a class. The compiler even has an internal list of standard classes it considers as stable/immutable (such as. String/Pair). Ability for users to add their own external classes is already planned, it is just that external annotations would provide a nice, consistent syntax for that. For serialization - external serializers are already implemented (although very instable), external annotations could provide a shortcut for their creation. But then yes, I'm not sure how to access the serializer for that class in the same manner as for same module.
d
Annotations are in general a bad solution to most problems as it forces dependencies that are in the wrong direction. This is especially the case for serialisation.
e
like I said, you can try adding the compose annotations to class bytecode - it does nothing. what the compose plugin does on the consumer side is check whether the compose compiler on the produce side recorded that a class was stable or immutable - whether automatically inferred or explicitly annotated - not check whether the class is currently annotated yes this could change in the future, but it's not something the kotlin compiler can help with
m
Yeah, I the Compose compiler currently adds and uses some
@Inferred
annotation and/or a static field. So it may not work OOTB (although it could). What I meant is that when adding such a feature, to flag external declarations, to compose, it would likely be easier and more consistent to the user (while not much harder for developers) to use external annotations, rather than passing a list of class names in gradle config, or how else it needs to be solved otherwise.
d
Far better to add things like a list of classes in gradle config. You may not have access to the classes in order to annotate them. Besides you shouldn't annotate with stuff that is about how the class is used. Upside down dependency!!!
e
and why would a Compose plugin change be a Kotlin feature request?
m
@ephemient I propose a foundation kotlin feature that the compose compiler could then take advantage of. I doubt this would be achievable for the plugin alone, even with the new frontend plugin API. But it's not just for compose - my 3 other examples, and I believe many others, but for those mentioned and other libraries, they all could benefit from this unified interface. @Dave
You may not have access to the classes in order to annotate them.
You mean a situation when a class is in the runtime, but not compiler classpath? So all my examples are concerned with annotating declarations that you actually use (that are visible by compiler); I assume that's the vast majority of use cases. Otherwise it is messing with internals. And it would not even work because external annotations only change how the compiler sees other declarations when compiling current module. But maybe you have some practical example, either with compose or anything else, when it's sensible to do so? Because then it actually could be made possible to annotate invisible external class by full name, perhaps with suppressible warning.
Besides you shouldn't annotate with stuff that is about how the class is used. Upside down dependency!!!
And how are external annotations worse when other approaches, e.g. list of classes for compose, xml with (non)nullables for java declarations, or `@Serializer(forClass = lib.Foo::class)`annotation in kotlinx.serialization? They all declare such a dependency, just with different syntax. Or perhaps all such attempts are tainted. May be, but then how would you advice go around their needs? For example: there is a `kotlinx.collections.immutable.ImmutableCollection`that we know is strictly immutable and want to benefit from this fact in a Compose application. But currently Compose compiler does not know anything about it. So we could: • Annotate `ImmutableCollection`at its source with
@Stable
. That would create a dependency of
kotlinx.collections.immutable
on compose runtime. And maybe its feasible there, as both libraries are 'kotlin official', but it would not be possible to do for all other libraries, say pcollections. • Add a special case for
ImmutableCollection
in compose compiler. That would create an informal dependency on
kotlinx.collections.immutable
. But like above, it's not possible to write down a list of all possible libraries someone would like to use alongside Compose. • So here we are, what's left is to declare a dependency between those two libraries at the use side. Maybe it's not that clean, but IMHO still better than the two possible dependencies above.