feat: Add Guppy type aliases#1645
Conversation
|
| Branch | ss/push-qxzxrlrrysko |
| Testbed | Linux |
Click to view all benchmark results
| Benchmark | hugr_bytes | Benchmark Result bytes x 1e3 (Result Δ%) | Upper Boundary bytes x 1e3 (Limit %) | hugr_nodes | Benchmark Result nodes x 1e3 (Result Δ%) | Upper Boundary nodes x 1e3 (Limit %) |
|---|---|---|---|---|---|---|
| tests/benchmarks/test_big_array.py::test_big_array_compile | 📈 view plot 🚷 view threshold | 158.77 x 1e3(0.00%)Baseline: 158.77 x 1e3 | 160.36 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 6.64 x 1e3(0.00%)Baseline: 6.64 x 1e3 | 6.71 x 1e3 (99.01%) |
| tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compile | 📈 view plot 🚷 view threshold | 27.53 x 1e3(0.00%)Baseline: 27.53 x 1e3 | 27.81 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 1.07 x 1e3(0.00%)Baseline: 1.07 x 1e3 | 1.08 x 1e3 (99.01%) |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1645 +/- ##
==========================================
+ Coverage 93.54% 93.59% +0.04%
==========================================
Files 133 134 +1
Lines 12640 12746 +106
==========================================
+ Hits 11824 11929 +105
- Misses 816 817 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
71b3742 to
fa6be65
Compare
fa6be65 to
3ec6f5c
Compare
This PR adds first-class Guppy type aliases via `guppy.type_alias(...)`. It introduces: - alias definitions in the type-definition pipeline - alias resolution during type parsing and instantiation Recursive and mutually recursive aliases are rejected. The recursion check follows the same general strategy already used for recursive structs/enums: - temporarily intercept alias instantiation while parsing the alias body - detect recursive re-entry instead of recursing forever - raise a compiler error For aliases, this now produces alias-specific diagnostics with: - a dedicated `RecursiveTypeAliasError` - notes pointing at the alias definitions involved in the cycle - a help hint suggesting how to break it Authored with OpenAI Codex. Closes #1066
3ec6f5c to
4678592
Compare
|
|
||
|
|
||
| def test_type_alias_bad_type_syntax(): | ||
| with pytest.raises(SyntaxError, match="Not a valid Guppy type: `foo bar`"): |
There was a problem hiding this comment.
Can you add an error tests for invalid aliases that fail at a later point, i.e. via a GuppyError instead of a SyntaxError?
| Help: Type aliases must eventually resolve to a non-alias type. Break the cycle | ||
| by inlining one alias or introducing a struct or enum wrapper. |
There was a problem hiding this comment.
Inlining can never break cycles? Similarly, I don't see how structs or enums would help?
| Note: | ||
| | | ||
| 3 | | ||
| 4 | MyAlias = guppy.type_alias("MyAlias") | ||
| | ------------------------------------- Alias `MyAlias` is part of this cycle |
There was a problem hiding this comment.
This note doesn't seem useful if it's a self cycle
| ) | ||
| defn = RawTypeAliasDef( | ||
| DefId.fresh(), | ||
| ty, |
There was a problem hiding this comment.
This sets the name of the definition as the type string which seems odd.
I see that you use a separate _alias_name function to resolve the name later by inspecting the Python scope. Note that this is quite different from how we handle naming of definition in other parts of the compiler. Usually, guppy.type_var etc take the definition name as an argument.
Checking the scope seems reasonable though, but it should happen here in the decorator! Also, the fallback probably shouldn't be the type string. Maybe, instead raise an error and force users to pass it as an argument?
| ctx = TypeParsingCtx(globals, allow_free_vars=True) | ||
| ty = type_from_ast(self.type_ast, ctx) | ||
| params = tuple(ctx.param_var_mapping.values()) |
There was a problem hiding this comment.
This looks like an attempt to support generic aliases? I think those would be useful, but we should either add tests for generic aliases or just set params = [] here
| err.add_sub_diagnostic( | ||
| RecursiveTypeAliasError.AliasNote(defn.defined_at, alias_name) | ||
| ) |
There was a problem hiding this comment.
This will fail to render if the aliases are defined in separate files
There was a problem hiding this comment.
While testing, I also found some other rendering errors. For example
Alias1 = guppy.type_alias("Alias2")
Alias2 = guppy.type_alias("Alias3")
Alias3 = guppy.type_alias("Alias2")
@guppy
def main(x: Alias1) -> Alias1:
return x
main.check()fails with
Error in sys.excepthook:
Traceback (most recent call last):
File "guppylang-internals/src/guppylang_internals/error.py", line 120, in hook
renderer.render_diagnostic(err.error)
File "guppylang-internals/src/guppylang_internals/diagnostic.py", line 323, in render_diagnostic
self.render_snippet(
File "guppylang-internals/src/guppylang_internals/diagnostic.py", line 419, in render_snippet
leading_whitespace = min(len(line) - len(line.lstrip()) for line in all_lines)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: min() iterable argument is empty
Not sure what's going on there...
| # cycle, so avoid attaching the same note more than once. | ||
| if any( | ||
| isinstance(child, RecursiveTypeAliasError.AliasNote) | ||
| and child.alias_name == alias_name |
There was a problem hiding this comment.
Going by name feels a bit fragile, can we store DefIds instead?
| if isinstance(err.error, RecursiveTypeAliasError): | ||
| _add_alias_note(err.error, defn, ctx.globals) |
There was a problem hiding this comment.
I don't understand why we need to add the notes while unwinding the exception. We already have the cycle, can't we just add all notes in one go?
There was a problem hiding this comment.
Can you add some tests for how the alias acyclicity checking interacts with structs and enums? I.e. add some integration and error tests that involve struct fields/enum variants whose types contain aliases.
This PR adds first-class Guppy type aliases via
guppy.type_alias(...).It introduces:
Recursive and mutually recursive aliases are rejected.
The recursion check follows the same general strategy already used for recursive structs/enums:
For aliases, this now produces alias-specific diagnostics with:
RecursiveTypeAliasErrorAI use: authored via locally iterating with OpenAI Codex, basing implementation on prior work in structs.
Closes #1066