ADR-0005: Preview Features
Status
Implemented
Summary
Introduce a preview feature gating mechanism that allows in-progress language features to be merged to main behind flags, enabling incremental development and test-driven implementation.
Context
Large features in Gruel often require multiple implementation phases spanning several commits or development sessions. Examples include:
- Inout parameters (future): Parser changes, exclusivity analysis, codegen calling convention
- Traits (future): Trait definitions, impl blocks, method resolution
When implementing these features in a single commit, several problems arise:
- All-or-nothing testing: Tests only exist for the complete feature, so partial implementations can't be validated
- Hard to debug: When tests fail, it's unclear which phase introduced the bug
- Can't ship incrementally: Work-in-progress can't be merged without breaking stable functionality
- Context loss: If development pauses, the incomplete state is hard to resume
We need a way to:
- Write tests for a feature before it's complete
- Merge partial implementations to main without breaking stable Gruel
- Track which features are in-progress vs stable
- Clearly communicate to users what's experimental
The key goal is continuous integration of incomplete work. Each commit—even for a partially-implemented feature—should be mergeable to main. This allows:
- Progress to be preserved across development sessions
- Multiple contributors to collaborate on large features
- Bisection to work for debugging regressions
- No long-lived feature branches that diverge from main
Decision
Introduce preview features - a gating mechanism for in-progress language features.
Compiler Flag
Code using preview features without the flag produces a clear error:
error: inout parameters require preview feature `inout_params`
--> source.gruel:1:12
|
1 | fn foo(inout x: i32) { }
| ^^^^^
|
= help: compile with `--preview inout_params` to enable
= note: preview features may change or be removed
Feature Registry
// gruel-compiler/src/features.rs
Semantic Analysis Gates
When analyzing code that requires a preview feature:
// In sema.rs - example for a hypothetical feature
..>
Spec Test Integration
Tests can declare which preview feature they require:
[[]]
= "inout_basic"
= "inout_params"
= """
fn increment(inout x: i32) {
x = x + 1;
}
fn main() -> i32 {
let mut n = 41;
increment(inout n);
n
}
"""
= 42
The test runner:
- Always runs preview tests (compiling with the appropriate
--previewflag) - Allows preview tests to fail without failing the overall suite
- Reports progress on each preview feature
Stabilization
When all tests for a preview feature pass:
- Remove
preview = "..."from spec tests - Remove the
require_preview()gate from sema - Remove the feature from the
PreviewFeatureenum - Update the ADR status to "Implemented"
The feature is now part of stable Gruel.
Implementation Phases
- Phase 1: Infrastructure - Add PreviewFeature enum, CompileOptions, CLI flag, error kind
- Phase 2: Test runner - Add preview field to test schema, update runner to handle preview tests
- Phase 3: First feature - Gate a new feature with the system (test_infra, see ADR-0016)
Consequences
Positive
- Incremental progress: Ship partial implementations behind gates
- Test-driven: Write tests before implementation is complete
- Clear status: Users know what's stable vs experimental
- Resumable: Can pause and resume feature work across sessions
- Debuggable: Smaller commits with focused changes
Negative
- Infrastructure overhead: More code to maintain
- Gate maintenance: Must remember to remove gates on stabilization
- Two test modes: Mental overhead of stable vs preview
Open Questions
Should we support multipleResolved: Multiple--previewflags, or a comma-separated list?--previewflags are supported; each flag enables one feature. A comma-separated form is not supported.
Future Work
- Preview feature progress dashboard
- Automatic tracking of preview feature test pass rates
References
- Rust's feature gates and editions
- TC39 proposal stages