icerpc / slicec

The Slice compiler library
Apache License 2.0
13 stars 5 forks source link

Modular Attributes #608

Closed InsertCreativityHere closed 1 year ago

InsertCreativityHere commented 1 year ago

Sister PR: https://github.com/icerpc/icerpc-csharp/pull/3356

This PR doesn't change any of the logic or rules for parsing/validating attributes, just the structure of them. Instead of storing attributes in nested enums (enum AttributeKind and enum CsAttributeKind) AttributeKind is now a trait, and each attribute just implements it. That's it.

And instead of attributes being enumerators, they're each their own stand-alone struct. So, everything about the Deprecated attribute is now centralized in struct Deprecated, defined in deprecated.rs. Before, everything was split up across multiple match statements in a huge file (AttributeKind).

These structs are fully self-contained and can be used like plug-and-play. Just define your attribute and implement these 2 functions:

And it just works; slicec calls and runs all this logic itself now. Languages don't need to patch or validate attributes anymore.

Actual changes:

Attributes handle their own parsing.

Each attribute has a parse_from function that takes an Unparsed attribute and returns itself. The attribute patcher checks each directive, and calls the corresponding attribute's parse_from. Instead of all the parsing being in a huge match statement, it's split up on each attribute.

Attributes handle their own validation.

Each attribute has a validate_on function that checks if it's applied to a valid element. This centralizes the validation for each validation making it easier to find, read, and change. Everything except validate_repeated_attributes was moved out of the attribute validator. Attributes are responsible to say what they're valid on, not the elements they're applied to.

Attribute parsing runs in it's own phase.

Instead of being done ad-hoc during slice parsing, attributes are patched directly after parsing completes. When the parser sees an attribute, it always creates an Unparsed attribute for the patcher to handle later.

Added the patch_attributes macro.

You give it a list of attributes and it generates a patching function for you to call. This process is language agnostic, so instead of each language needing a boilerplate attribute patcher, all that can be deleted, and they just use this macro instead. See:

Simplified lookup functions.

Instead of passing typed predicate functions, now you just pass a type directly. field.find_attribute(match_compress) becomes field.find_attribute::<Compress>() All the predicate functions were deleted since they're redundant now.

Stronger typing.

Instead of returning a tuple of arguments, this function returns a Compress struct now. This leads to safer and more readable code than getting back (bool, bool).

Adds a new Attributables wrapper type.

This works just like Types and concrete_type. Everything that implements Attributable implements this. This wrapper is necessary to drive the automatic parsing/validation, since it needs to know every type it might encounter.

Moved attribute parsing helpers out of slice-cs and into slicec

They're now in the utils/attribute_parsing_util.rs file. These functions are generally useful, and not language specific. No point rewritting them in every single language when we can just write them once in one place.