ZE-004 - Variadic Annotations
Variadic Annotations
This proposal is still a draft and is subject to change. Please do not cite or reference it as a finalized design.
Features described here may not be implemented as described and cannot be used right now.
Introduction
This proposal introduces variadic annotations to Zirric, enabling annotations to accept a variable number of arguments. This feature addresses the need for multi-instance constraints (e.g., @Requires(Countable, Iterable)) while maintaining a clean and expressive syntax.
Variadic annotations are particularly useful for:
- Representing requirements or constraints on fields, parameters, or types.
- Improving readability and reducing verbosity compared to alternatives like
@HasAll([Countable, Iterable]).
Motivation
Problem
Zirric currently lacks a concise way to specify multiple constraints or requirements in annotations. For example, to enforce that a parameter must satisfy both Countable and Iterable, users must use workarounds like @HasAll([Countable, Iterable]), which is less readable and feels unnatural.
Use Cases
- Type Constraints: Annotating parameters or fields to require multiple traits or interfaces.
func process(@Requires(Countable, Iterable) data) {} - Tooling and Compile-Time Checks: Enabling tools (e.g., LSP, compilers) to validate that values meet multiple constraints.
- Library Design: Allowing library authors to define flexible APIs that accept variadic constraints.
Benefits
- Readability:
@Requires(Countable, Iterable)is more intuitive than@HasAll([Countable, Iterable]). - Flexibility: Supports empty lists, optional constraints, and future extensibility.
- Consistency: Aligns with variadic functions in other languages, making it familiar to developers.
Proposed Solution
Syntax
Introduce a @Variadic annotation to mark annotation fields as accepting a variable number of arguments. When used, the field’s type is treated as an Array of the specified element type.
Example: Defining a Variadic Annotation
annotation Requires {
@Variadic()
@Elements(Type) // Accepts types or type-like annotations
constraints
}
Example: Using a Variadic Annotation
// Function with variadic constraints
func process(@Requires(Countable, Iterable) data) {
// `data` must satisfy both `Countable` and `Iterable`
}
// Valid calls
process([1, 2, 3]) // Array of Int (assuming Int is Countable and Iterable)
process([]) // Empty array
// Invalid call (if String doesn't satisfy Iterable)
process("hello") // Compiler error: String is not Iterable
Behavior
- Type Handling: Variadic arguments are always treated as an
Arrayof the specified element type (e.g.,@Elements(Type)). - Empty Lists: Supported for optional constraints (e.g.,
@Requires()). - Type Safety: The compiler enforces that all provided arguments match the element type (e.g.,
Type).
Detailed Design
Annotation Definition
- The
@Variadicannotation marks a field within another annotation as variadic. - The field’s type is implicitly an
Arrayof the type specified by@Elements.annotation VariadicExample { @Variadic() @Elements(Int) // Accepts variadic Int arguments numbers }
Usage in Functions
- Variadic annotations can be applied to function parameters, fields, or types.
func example(@VariadicExample(1, 2, 3) nums) {}
Compiler and Tooling Support
- Parsing: The compiler treats variadic annotation fields as arrays during parsing.
- Type Checking: Ensures all arguments match the element type specified by
@Elements. - Error Messages: Provides clear errors for type mismatches or invalid usage.
Edge Cases
- Empty Lists:
@Requires()is valid and results in an empty array of constraints. - Single Argument:
@Requires(Countable)is treated as an array with one element. - Type Mismatches: The compiler rejects arguments that don’t match
@Elements.
Changes to the Standard Library
New Annotations
@Variadic:- Marks a field as accepting a variable number of arguments.
- Only valid within annotation definitions.
@Elements:- Specifies the type of elements in a variadic field (e.g.,
@Elements(Type)). - Required for variadic fields to enforce type safety.
- Specifies the type of elements in a variadic field (e.g.,
Example Additions
// Standard library addition
annotation Variadic {}
annotation Elements {
type
}
Alternatives Considered
@HasAll([...])Syntax:- Pros: No new syntax required.
- Cons: Less readable and feels like a workaround.
Dedicated Grouping Syntax (e.g.,
@Requires{Countable, Iterable}):- Pros: Clean and intuitive.
- Cons: Doesn’t translate to function arguments and requires new grammar rules.
Multiple Annotations (e.g.,
@Requires(Countable) @Requires(Iterable)):- Pros: Simple and explicit.
- Cons: Verbose and ambiguous for single-instance annotations.
Why @Variadic Was Chosen:
- Balances flexibility and readability.
- Works seamlessly with function arguments and fields.
- Aligns with Zirric’s existing annotation system.
Acknowledgements
- Prior Art: Inspired by variadic functions in languages like C, JavaScript, and Rust.
- Contributors: @vknabel for designing the proposal.
Open Questions
- Should
@Variadicbe restricted to specific contexts (e.g., only for constraints)? - How should the compiler handle type mismatches in variadic arguments?
- Should there be a shorthand syntax for common cases (e.g.,
@Requires[Countable, Iterable])? - Should there be spreads to pass arrays as variadic arguments (e.g.,
@Requires(...myArray))? - Should
@Variadicrequire an optionalminormaxparameter or arangeto enforce bounds on the number of arguments?