Improve macro compile-time diagnostics and key resolution validation#73
Improve macro compile-time diagnostics and key resolution validation#73
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
18044ea to
29bad92
Compare
There was a problem hiding this comment.
adiman9 has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
29bad92 to
8f23f4c
Compare
Greptile SummaryThis PR wires up a comprehensive compile-time diagnostic and validation pipeline for Key changes:
Issues found:
Confidence Score: 3/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["#[hyperstack] macro invoked"] --> B["parse_proto_files_from_attr"]
B --> C{"IDL files?"}
C -- yes --> D["process_idl_spec"]
C -- no --> E["Collect entity structs"]
E --> F["parse_recognized_field_attribute\n(#[map] / #[event] / #[resolve] / ...)"]
F --> G["Validate strategies, conditions,\nfield paths eagerly"]
G --> H["process_entity_struct_with_idl"]
H --> I["validate_semantics"]
I --> J["validate_key_resolution_paths"]
I --> K["validate_mapping_references\n(IDL field lookup)"]
I --> L["validate_computed_fields\n(cycle detection)"]
I --> M["validate_views\n(sort-field exists)"]
I --> N["validate_resolve_specs\n(from/schedule_at fields exist)"]
J --> J1["validate_source_handler_keys\n(account + instruction sources)"]
J --> J2["validate_event_handler_keys"]
J --> J3["validate_instruction_hook_keys\n(#[derive_from])"]
H --> O["generate AST + embedded JSON spec"]
O --> P{"write .hyperstack/ file"}
P -- success --> Q["generate_multi_entity_builder"]
P -- failure --> R["eprintln! warning\n(non-fatal)"]
R --> Q
Q --> S["Emit final TokenStream"]
|
| @@ -69,7 +72,7 @@ pub fn parse_url_template(s: &str) -> Vec<UrlTemplatePart> { | |||
| } | |||
| let close = rest[open..] | |||
| .find('}') | |||
| .expect("Unclosed '{' in URL template") | |||
| .ok_or_else(|| syn::Error::new(span, format!("Unclosed '{{' in URL template: {s}")))? | |||
| + open; | |||
| let field_ref = rest[open + 1..close].trim().to_string(); | |||
| parts.push(UrlTemplatePart::FieldRef(field_ref)); | |||
| @@ -80,7 +83,7 @@ pub fn parse_url_template(s: &str) -> Vec<UrlTemplatePart> { | |||
| parts.push(UrlTemplatePart::Literal(rest.to_string())); | |||
There was a problem hiding this comment.
Empty field reference in URL template not validated at compile time
parse_url_template now correctly rejects unclosed braces ({ without }). However, it does not validate that the captured field_ref is non-empty after trimming. A template such as "https://api.example.com/{}/data" or "https://api.example.com/{ }/data" will silently produce a UrlTemplatePart::FieldRef(""), which causes a runtime panic/error when the resolver attempts to look up an empty field path — with no compile-time warning.
Since the PR's intent is to surface these errors eagerly during macro expansion, this case is a natural extension of the same validation:
let field_ref = rest[open + 1..close].trim().to_string();
if field_ref.is_empty() {
return Err(syn::Error::new(
span,
format!("Empty field reference '{{}}' in URL template: {s}"),
));
}
parts.push(UrlTemplatePart::FieldRef(field_ref));Prompt To Fix With AI
This is a comment left during a code review.
Path: hyperstack-macros/src/stream_spec/entity.rs
Line: 65-83
Comment:
**Empty field reference in URL template not validated at compile time**
`parse_url_template` now correctly rejects unclosed braces (`{` without `}`). However, it does not validate that the captured `field_ref` is non-empty after trimming. A template such as `"https://api.example.com/{}/data"` or `"https://api.example.com/{ }/data"` will silently produce a `UrlTemplatePart::FieldRef("")`, which causes a runtime panic/error when the resolver attempts to look up an empty field path — with no compile-time warning.
Since the PR's intent is to surface these errors eagerly during macro expansion, this case is a natural extension of the same validation:
```rust
let field_ref = rest[open + 1..close].trim().to_string();
if field_ref.is_empty() {
return Err(syn::Error::new(
span,
format!("Empty field reference '{{}}' in URL template: {s}"),
));
}
parts.push(UrlTemplatePart::FieldRef(field_ref));
```
How can I resolve this? If you propose a fix, please make it concise.| fn validate_event_references( | ||
| entity_name: &str, | ||
| events_by_instruction: &HashMap<String, Vec<(String, parse::EventAttribute, syn::Type)>>, | ||
| known_fields: &HashSet<String>, | ||
| available_fields: &[String], | ||
| idls: IdlLookup, | ||
| errors: &mut ErrorCollector, | ||
| ) { | ||
| for (instruction_key, event_mappings) in events_by_instruction { | ||
| for (_target_field, event_attr, _field_type) in event_mappings { | ||
| let instruction_lookup = resolve_instruction_lookup(event_attr, instruction_key, idls); | ||
|
|
||
| let (idl, instruction_name) = match instruction_lookup { | ||
| Ok(value) => value, | ||
| Err(error) => { | ||
| errors.push(idl_error_to_syn( | ||
| event_attr.instruction_span.unwrap_or(event_attr.attr_span), | ||
| error, | ||
| )); | ||
| continue; | ||
| } | ||
| }; | ||
|
|
||
| for field_spec in &event_attr.capture_fields { | ||
| if let Err(error) = | ||
| validate_instruction_field_spec(idl, &instruction_name, field_spec) | ||
| { | ||
| errors.push(idl_error_to_syn(field_spec.ident.span(), error)); | ||
| } | ||
| } | ||
|
|
||
| if let Some(field_spec) = &event_attr.lookup_by { | ||
| if let Err(error) = | ||
| validate_instruction_field_spec(idl, &instruction_name, field_spec) | ||
| { | ||
| errors.push(idl_error_to_syn(field_spec.ident.span(), error)); | ||
| } | ||
| } | ||
|
|
||
| if let Some(join_on) = &event_attr.join_on { | ||
| let reference = join_on.ident.to_string(); | ||
| if !known_fields.contains(&reference) { | ||
| errors.push(entity_field_error( | ||
| entity_name, | ||
| &reference, | ||
| "join_on field", | ||
| join_on.ident.span(), | ||
| available_fields, | ||
| )); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
join_on entity-field validation is gated behind a successful IDL lookup
validate_event_references validates event_attr.join_on as a reference to a known entity field (lines 699–710). This check is only reached if the IDL lookup for the instruction succeeds — when it fails, the function continues before reaching the join_on block.
join_on on an #[event] attribute names an entity field, not an instruction argument, so its validity is entirely independent of whether the IDL is available. A stale IDL or an unrecognised instruction path would suppress the entity-field check, masking a misspelled join_on target:
// validate join_on against entity fields regardless of IDL availability
if let Some(join_on) = &event_attr.join_on {
let reference = join_on.ident.to_string();
if !known_fields.contains(&reference) {
errors.push(entity_field_error(
entity_name, &reference, "join_on field",
join_on.ident.span(), available_fields,
));
}
}
let (idl, instruction_name) = match instruction_lookup {
Ok(value) => value,
Err(error) => {
errors.push(idl_error_to_syn(...));
continue;
}
};
// IDL-dependent checks follow ...Prompt To Fix With AI
This is a comment left during a code review.
Path: hyperstack-macros/src/validation/mod.rs
Line: 660-713
Comment:
**`join_on` entity-field validation is gated behind a successful IDL lookup**
`validate_event_references` validates `event_attr.join_on` as a reference to a known entity field (lines 699–710). This check is only reached if the IDL lookup for the instruction succeeds — when it fails, the function `continue`s before reaching the `join_on` block.
`join_on` on an `#[event]` attribute names an **entity** field, not an instruction argument, so its validity is entirely independent of whether the IDL is available. A stale IDL or an unrecognised instruction path would suppress the entity-field check, masking a misspelled `join_on` target:
```rust
// validate join_on against entity fields regardless of IDL availability
if let Some(join_on) = &event_attr.join_on {
let reference = join_on.ident.to_string();
if !known_fields.contains(&reference) {
errors.push(entity_field_error(
entity_name, &reference, "join_on field",
join_on.ident.span(), available_fields,
));
}
}
let (idl, instruction_name) = match instruction_lookup {
Ok(value) => value,
Err(error) => {
errors.push(idl_error_to_syn(...));
continue;
}
};
// IDL-dependent checks follow ...
```
How can I resolve this? If you propose a fix, please make it concise.
Summary
hyperstack-macrosvalidation pipeline so attribute parsing preserves source spans, validates arguments eagerly, and reports actionable compile-time errors instead of opaque expansion failures#[map],#[resolve],#[aggregate],#[event],#[view], and top-level macro arguments, plus resolver and computed-field validation paths#[derive_from]hooks so entities must prove a path back to their primary key_addressas aliases for the underlying lookup field when resolving handlersWhat Changed
hyperstack-macrosto accumulate and surface multiple validation errors with source-aware spanslookup_by, or resolver hooksCommit Breakdown
fix: surface hyperstack macro validation failures during expansionchore: refresh ore generated stack artifactschore: format touched runtime cratesfix: validate handler key resolution paths during macro expansionReviewer Notes
bb6d515and8f23f4c; the other two commits are generated artifact refreshes and formatting follow-upTesting
hyperstack-macros/tests/