@layerzerolabs/framework-solana-anchor-trait 0.2.61 → 0.2.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +3 -0
- package/README.md +5 -0
- package/SPEC.md +318 -0
- package/examples/shapes/Cargo.lock +4126 -0
- package/examples/shapes/Cargo.toml +16 -0
- package/examples/shapes/macros/src/audit.rs +15 -0
- package/examples/shapes/macros/src/instructions/clear.rs +19 -0
- package/examples/shapes/macros/src/instructions/compute_area.rs +18 -20
- package/examples/shapes/macros/src/instructions/compute_perimeter.rs +18 -20
- package/examples/shapes/macros/src/instructions/get_dimension.rs +19 -0
- package/examples/shapes/macros/src/instructions/get_dimension_count.rs +19 -17
- package/examples/shapes/macros/src/instructions/mod.rs +8 -0
- package/examples/shapes/macros/src/instructions/reset.rs +50 -0
- package/examples/shapes/macros/src/instructions/scale.rs +52 -0
- package/examples/shapes/macros/src/lib.rs +14 -11
- package/examples/shapes/macros/src/shapes.rs +21 -26
- package/examples/shapes/macros/src/state_types.rs +14 -0
- package/examples/shapes/programs/ellipse/Cargo.toml +7 -0
- package/examples/shapes/programs/ellipse/src/lib.rs +69 -17
- package/examples/shapes/programs/ellipse/tests/ellipse/helpers.rs +110 -0
- package/examples/shapes/programs/ellipse/tests/ellipse/negative.rs +49 -0
- package/examples/shapes/programs/ellipse/tests/ellipse/positive.rs +231 -0
- package/examples/shapes/programs/ellipse/tests/ellipse.rs +23 -0
- package/examples/shapes/programs/triangle/Cargo.toml +7 -0
- package/examples/shapes/programs/triangle/src/lib.rs +69 -16
- package/examples/shapes/programs/triangle/tests/triangle/helpers.rs +123 -0
- package/examples/shapes/programs/triangle/tests/triangle/negative.rs +49 -0
- package/examples/shapes/programs/triangle/tests/triangle/positive.rs +225 -0
- package/examples/shapes/programs/triangle/tests/triangle.rs +23 -0
- package/examples/shapes/target/deploy/shapes_ellipse-keypair.json +1 -0
- package/examples/shapes/target/deploy/shapes_ellipse.so +0 -0
- package/examples/shapes/target/deploy/shapes_triangle-keypair.json +1 -0
- package/examples/shapes/target/deploy/shapes_triangle.so +0 -0
- package/package.json +14 -7
- package/src/assembly.rs +41 -6
- package/src/generation.rs +108 -131
- package/src/lib.rs +6 -4
- package/src/overrides.rs +56 -81
- package/tests/assembly.rs +154 -23
- package/tests/common/mod.rs +68 -14
- package/tests/generation.rs +137 -65
- package/tests/override.rs +93 -55
- package/tests/snapshots/snapshots__tests__snapshot_all_defaults.snap +32 -5
- package/tests/snapshots/snapshots__tests__snapshot_full_override.snap +35 -7
- package/tests/snapshots/snapshots__tests__snapshot_handler_override.snap +37 -7
- package/tests/snapshots/snapshots__tests__snapshot_mixed_overrides.snap +40 -9
- package/tests/snapshots/snapshots__tests__snapshot_with_extend_macro.snap +82 -0
- package/tests/snapshots.rs +51 -18
- package/examples/shapes/Anchor.toml +0 -19
- package/examples/shapes/programs/ellipse/src/state.rs +0 -9
- package/examples/shapes/programs/triangle/src/state.rs +0 -9
package/src/generation.rs
CHANGED
|
@@ -1,60 +1,97 @@
|
|
|
1
1
|
//! Instruction code generation with override level resolution.
|
|
2
2
|
//!
|
|
3
3
|
//! - **Default**: No override → emit default impl + default entrypoint.
|
|
4
|
-
//! - **
|
|
5
|
-
//! dispatches to implementor's handler.
|
|
6
|
-
//! - **Full Override**: Override with custom context type → skip default impl, entrypoint
|
|
7
|
-
//! dispatches to implementor's handler with custom type.
|
|
4
|
+
//! - **Override**: Custom handler present → emit default impl (always available), entrypoint
|
|
5
|
+
//! dispatches to implementor's handler using whatever context type the handler declares.
|
|
8
6
|
|
|
9
7
|
use proc_macro2::{Ident, Span, TokenStream};
|
|
10
8
|
use quote::{quote, ToTokens};
|
|
9
|
+
use syn::{ItemImpl, ItemStruct};
|
|
11
10
|
|
|
12
|
-
use crate::overrides::
|
|
11
|
+
use crate::overrides::InstructionOverrides;
|
|
13
12
|
use crate::InstructionSet;
|
|
14
13
|
|
|
15
14
|
/// Descriptor for one instruction's code generation.
|
|
16
15
|
pub struct InstructionSpec<Ctx> {
|
|
17
|
-
/// Parameter type as a token stream (e.g. `Some(quote!(::
|
|
16
|
+
/// Parameter type as a token stream (e.g. `Some(quote!(::lz_oapp::types::SetPeerParams))`).
|
|
18
17
|
/// Use `None` for instructions with no params.
|
|
19
18
|
pub params_type: Option<TokenStream>,
|
|
20
19
|
/// Return type as a token stream (e.g. `quote!(())` or `quote!([u8; 32])`).
|
|
21
20
|
pub return_type: TokenStream,
|
|
22
|
-
/// When `true`, the entrypoint passes
|
|
23
|
-
/// When `false
|
|
21
|
+
/// When `true`, the entrypoint passes `&ctx` (immutable).
|
|
22
|
+
/// When `false`, it passes `&mut ctx`.
|
|
24
23
|
pub is_view: bool,
|
|
25
|
-
/// - `Some(...)` — provided instruction (
|
|
26
|
-
/// - `None` — **required** instruction: the implementor must provide an override handler
|
|
27
|
-
///
|
|
24
|
+
/// - `Some(...)` — provided instruction (Default and Override levels valid).
|
|
25
|
+
/// - `None` — **required** instruction: the implementor must provide an override handler.
|
|
26
|
+
/// Omitting the override produces a compile error.
|
|
28
27
|
pub default_impl: Option<DefaultImpl<Ctx>>,
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
/// Default impl generation for an instruction (accounts struct + handler).
|
|
32
31
|
///
|
|
33
|
-
/// Groups the
|
|
32
|
+
/// Groups the fields that are only meaningful when an instruction
|
|
34
33
|
/// has a framework-provided default implementation.
|
|
35
34
|
pub struct DefaultImpl<Ctx> {
|
|
36
|
-
/// Name of the default accounts struct, e.g. `format_ident!("StdSetPeer")`.
|
|
37
|
-
/// Also used to detect Handler Override vs Full Override (string comparison with handler's
|
|
38
|
-
/// accounts type).
|
|
39
|
-
pub accounts_ident: Ident,
|
|
40
35
|
/// Name of the default handler method on the context struct, e.g. `format_ident!("apply")`.
|
|
41
36
|
/// At the Default level, the entrypoint calls `StdAccounts::<this>(...)`.
|
|
42
37
|
pub handler_ident: Ident,
|
|
43
|
-
///
|
|
44
|
-
|
|
45
|
-
/// For mutable instructions: `fn(&mut Context<Self>, &Params) -> Result<R>`.
|
|
46
|
-
/// For view instructions: `fn(&Context<Self>, &Params) -> Result<R>`.
|
|
47
|
-
/// Omit the params argument when `params_type = None`.
|
|
48
|
-
pub generator: fn(&Ctx, &InstructionSpec<Ctx>) -> TokenStream,
|
|
38
|
+
/// Returns the complete accounts struct and its impl block as typed AST.
|
|
39
|
+
pub generator: fn(&Ctx, &InstructionSpec<Ctx>) -> DefaultAccounts,
|
|
49
40
|
}
|
|
50
41
|
|
|
51
|
-
impl
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
/// Complete accounts struct and its handler impl block.
|
|
43
|
+
///
|
|
44
|
+
/// The framework uses this template to produce two outputs:
|
|
45
|
+
/// 1. The concrete struct + impl — emitted as-is via [`to_definition`](Self::to_definition).
|
|
46
|
+
/// 2. A `macro_rules!` rule — built by decomposing the struct into attrs/fields and replacing the
|
|
47
|
+
/// name with `$name` via [`to_extend_rule`](Self::to_extend_rule).
|
|
48
|
+
pub struct DefaultAccounts {
|
|
49
|
+
/// Complete struct definition including attributes, name, generics, and fields.
|
|
50
|
+
pub struct_type: ItemStruct,
|
|
51
|
+
/// Complete impl block with handler methods. Must use `Context<Self>` so the
|
|
52
|
+
/// methods work regardless of the struct name.
|
|
53
|
+
pub impl_block: ItemImpl,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl DefaultAccounts {
|
|
57
|
+
/// Returns the struct ident (e.g. `StdSetPeer`) — the single source of truth.
|
|
58
|
+
pub fn accounts_ident(&self) -> &Ident {
|
|
59
|
+
&self.struct_type.ident
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Emits the struct and impl block.
|
|
63
|
+
pub fn to_definition(&self) -> TokenStream {
|
|
64
|
+
let s = &self.struct_type;
|
|
65
|
+
let i = &self.impl_block;
|
|
66
|
+
quote! { #s #i }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Assembles one `macro_rules!` rule for extending the default accounts.
|
|
70
|
+
///
|
|
71
|
+
/// Decomposes the struct into attributes and fields, then re-emits with
|
|
72
|
+
/// `$name` replacing the struct ident and `$($extra)*` appended to the fields.
|
|
73
|
+
pub fn to_extend_rule(&self, instruction_name: &str) -> TokenStream {
|
|
74
|
+
let attrs = &self.struct_type.attrs;
|
|
75
|
+
let fields: Vec<_> = match &self.struct_type.fields {
|
|
76
|
+
syn::Fields::Named(f) => f.named.iter().collect(),
|
|
77
|
+
_ => unreachable!("accounts structs always have named fields"),
|
|
78
|
+
};
|
|
79
|
+
let impl_items = &self.impl_block.items;
|
|
80
|
+
let rule_ident = Ident::new(instruction_name, Span::call_site());
|
|
81
|
+
|
|
82
|
+
quote! {
|
|
83
|
+
(#rule_ident, $name:ident, { $($extra:tt)* }) => {
|
|
84
|
+
#(#attrs)*
|
|
85
|
+
pub struct $name<'info> {
|
|
86
|
+
#(#fields,)*
|
|
87
|
+
$($extra)*
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
impl $name<'_> {
|
|
91
|
+
#(#impl_items)*
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
58
95
|
}
|
|
59
96
|
}
|
|
60
97
|
|
|
@@ -66,31 +103,18 @@ pub struct GeneratedInstructions {
|
|
|
66
103
|
pub default_impls: TokenStream,
|
|
67
104
|
/// All entrypoint function definitions (placed inside the program module).
|
|
68
105
|
pub entrypoints: TokenStream,
|
|
106
|
+
/// Collected `macro_rules!` rules for extending default accounts structs.
|
|
107
|
+
pub extend_rules: TokenStream,
|
|
69
108
|
}
|
|
70
109
|
|
|
71
110
|
impl GeneratedInstructions {
|
|
72
111
|
fn extend(&mut self, other: Self) {
|
|
73
112
|
self.default_impls.extend(other.default_impls);
|
|
74
113
|
self.entrypoints.extend(other.entrypoints);
|
|
114
|
+
self.extend_rules.extend(other.extend_rules);
|
|
75
115
|
}
|
|
76
116
|
}
|
|
77
117
|
|
|
78
|
-
/// Resolved override: everything needed to assemble the entrypoint.
|
|
79
|
-
struct ResolvedOverride {
|
|
80
|
-
/// Tokens for the default accounts struct + handler impl (emitted outside the
|
|
81
|
-
/// program module). Empty when the override fully replaces the default context.
|
|
82
|
-
default_impl: TokenStream,
|
|
83
|
-
/// Function-level generics from the override handler (e.g. `<'info>`).
|
|
84
|
-
/// Empty at the Default level since the generated entrypoint needs no extra generics.
|
|
85
|
-
fn_generics: TokenStream,
|
|
86
|
-
/// The `ctx` parameter declaration: `ctx: Context<T>` or `mut ctx: Context<T>`.
|
|
87
|
-
ctx_param: TokenStream,
|
|
88
|
-
/// Body expression for instructions that take params: `handler(ctx, params)`.
|
|
89
|
-
call_with_params: TokenStream,
|
|
90
|
-
/// Body expression for parameter-less instructions: `handler(ctx)`.
|
|
91
|
-
call_no_params: TokenStream,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
118
|
/// Generates code for all instructions and merges into a single output.
|
|
95
119
|
pub fn generate_instructions<Ix: InstructionSet>(
|
|
96
120
|
ctx: &Ix::Ctx,
|
|
@@ -103,7 +127,7 @@ pub fn generate_instructions<Ix: InstructionSet>(
|
|
|
103
127
|
Ok(out)
|
|
104
128
|
}
|
|
105
129
|
|
|
106
|
-
/// Generates code for a single instruction
|
|
130
|
+
/// Generates code for a single instruction.
|
|
107
131
|
///
|
|
108
132
|
/// Returns `Err` if the instruction is required (`default_impl = None`)
|
|
109
133
|
/// and the implementor did not provide an override handler.
|
|
@@ -117,110 +141,63 @@ fn generate_instruction<Ix: InstructionSet>(
|
|
|
117
141
|
|
|
118
142
|
// Required instruction with no override → compile error.
|
|
119
143
|
if handler.is_none() && spec.default_impl.is_none() {
|
|
120
|
-
let cfg = &overrides.config;
|
|
121
144
|
return Err(syn::Error::new(
|
|
122
145
|
Span::call_site(),
|
|
123
146
|
format!(
|
|
124
147
|
"instruction `{}` has no default implementation; \
|
|
125
|
-
provide a
|
|
148
|
+
provide a `fn {}(...)` annotated with #[{}]",
|
|
126
149
|
ix_name.canonical_name(),
|
|
127
|
-
cfg.attr_name,
|
|
128
150
|
ix_name.canonical_name(),
|
|
151
|
+
&overrides.config.attr_name,
|
|
129
152
|
),
|
|
130
153
|
));
|
|
131
154
|
}
|
|
132
155
|
|
|
133
|
-
|
|
134
|
-
|
|
156
|
+
// Always emit the default accounts when one exists (available even under override).
|
|
157
|
+
// Also collect the extend rule for the macro_rules! arm.
|
|
158
|
+
let default_accounts = spec.default_impl.as_ref().map(|d| (d.generator)(ctx, &spec));
|
|
159
|
+
let (default_impls, extend_rules) = match &default_accounts {
|
|
160
|
+
Some(da) => (da.to_definition(), da.to_extend_rule(ix_name.canonical_name())),
|
|
161
|
+
None => (TokenStream::new(), TokenStream::new()),
|
|
162
|
+
};
|
|
135
163
|
|
|
136
|
-
|
|
137
|
-
|
|
164
|
+
// Assemble entrypoint fragments from spec + dispatch target.
|
|
165
|
+
let entry_fn_ident = syn::Ident::new(ix_name.canonical_name(), Span::call_site());
|
|
138
166
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
167
|
+
// Resolve dispatch target: override handler vs default handler.
|
|
168
|
+
let (fn_generics, context_type, dispatch) = match handler {
|
|
169
|
+
Some(h) => {
|
|
170
|
+
let wrapper = &h.wrapper_mod;
|
|
171
|
+
let ctx_type = &h.context_type;
|
|
172
|
+
(h.fn_generics.to_token_stream(), quote!(#ctx_type), quote!(#wrapper::#entry_fn_ident))
|
|
146
173
|
},
|
|
147
|
-
None =>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
None => {
|
|
175
|
+
let default = spec.default_impl.as_ref().expect("validated above");
|
|
176
|
+
let accounts = default_accounts.as_ref().unwrap().accounts_ident();
|
|
177
|
+
let handler_fn = &default.handler_ident;
|
|
178
|
+
(TokenStream::new(), quote!(Context<#accounts>), quote!(#accounts::#handler_fn))
|
|
151
179
|
},
|
|
152
180
|
};
|
|
181
|
+
let return_type = &spec.return_type;
|
|
182
|
+
let (ref_token, ctx_decl) = if spec.is_view {
|
|
183
|
+
(quote!(&), quote!(ctx: #context_type))
|
|
184
|
+
} else {
|
|
185
|
+
(quote!(&mut), quote!(mut ctx: #context_type))
|
|
186
|
+
};
|
|
153
187
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
/// - **Override present** → dispatch to the implementor's wrapper handler. Emit default impl only
|
|
160
|
-
/// if the handler reuses the default context (Handler Override).
|
|
161
|
-
/// - **No override** → dispatch to the default handler directly.
|
|
162
|
-
fn resolve_override<Ctx>(
|
|
163
|
-
override_handler: Option<&HandlerMeta>,
|
|
164
|
-
spec: &InstructionSpec<Ctx>,
|
|
165
|
-
ctx: &Ctx,
|
|
166
|
-
) -> ResolvedOverride {
|
|
167
|
-
match (override_handler, &spec.default_impl) {
|
|
168
|
-
// Handler Override or Full Override — the implementor provided a custom handler.
|
|
169
|
-
//
|
|
170
|
-
// Handler Override (context type matches default): emit default impl so the
|
|
171
|
-
// override handler can delegate back to the default accounts struct.
|
|
172
|
-
// Full Override (different context type): skip default impl entirely; the
|
|
173
|
-
// implementor supplies their own accounts struct and handler logic.
|
|
174
|
-
//
|
|
175
|
-
// In both cases the entrypoint dispatches to the implementor's wrapper handler,
|
|
176
|
-
// passing `ctx` and `params` by value (ownership) so they can freely mutate,
|
|
177
|
-
// destructure, or forward them.
|
|
178
|
-
(Some(handler), spec_default) => {
|
|
179
|
-
let default_impl = if spec.uses_default(&handler.accounts_type) {
|
|
180
|
-
(spec_default.as_ref().unwrap().generator)(ctx, spec)
|
|
181
|
-
} else {
|
|
182
|
-
TokenStream::new()
|
|
183
|
-
};
|
|
184
|
-
let wrapper = &handler.wrapper_mod;
|
|
185
|
-
let handler_fn = &handler.fn_ident;
|
|
186
|
-
let context_type = &handler.context_type;
|
|
187
|
-
ResolvedOverride {
|
|
188
|
-
default_impl,
|
|
189
|
-
fn_generics: handler.fn_generics.to_token_stream(),
|
|
190
|
-
ctx_param: ctx_param_tokens(spec.is_view, quote!(#context_type)),
|
|
191
|
-
call_with_params: quote!(#wrapper::#handler_fn(ctx, params)),
|
|
192
|
-
call_no_params: quote!(#wrapper::#handler_fn(ctx)),
|
|
188
|
+
// Emit the entrypoint function, with or without a params argument.
|
|
189
|
+
let entrypoints = match &spec.params_type {
|
|
190
|
+
Some(params_type) => quote! {
|
|
191
|
+
pub fn #entry_fn_ident #fn_generics (#ctx_decl, params: #params_type) -> Result<#return_type> {
|
|
192
|
+
#dispatch(#ref_token ctx, ¶ms)
|
|
193
193
|
}
|
|
194
194
|
},
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// Unlike override handlers, the default handler receives `ctx` by reference
|
|
199
|
-
// (`&` for views, `&mut` for mutable) because it is a method on the default
|
|
200
|
-
// accounts struct, not a standalone function the implementor owns.
|
|
201
|
-
(None, Some(default)) => {
|
|
202
|
-
let accounts = &default.accounts_ident;
|
|
203
|
-
let handler_fn = &default.handler_ident;
|
|
204
|
-
let ref_token = if spec.is_view { quote!(&) } else { quote!(&mut) };
|
|
205
|
-
ResolvedOverride {
|
|
206
|
-
default_impl: (default.generator)(ctx, spec),
|
|
207
|
-
fn_generics: TokenStream::new(),
|
|
208
|
-
ctx_param: ctx_param_tokens(spec.is_view, quote!(Context<#accounts>)),
|
|
209
|
-
call_with_params: quote!(#accounts::#handler_fn(#ref_token ctx, ¶ms)),
|
|
210
|
-
call_no_params: quote!(#accounts::#handler_fn(#ref_token ctx)),
|
|
195
|
+
None => quote! {
|
|
196
|
+
pub fn #entry_fn_ident #fn_generics (#ctx_decl) -> Result<#return_type> {
|
|
197
|
+
#dispatch(#ref_token ctx)
|
|
211
198
|
}
|
|
212
199
|
},
|
|
213
|
-
|
|
214
|
-
// override_handler or default_impl is present before calling this function.
|
|
215
|
-
(None, None) => unreachable!("validated above"),
|
|
216
|
-
}
|
|
217
|
-
}
|
|
200
|
+
};
|
|
218
201
|
|
|
219
|
-
|
|
220
|
-
fn ctx_param_tokens(is_view: bool, context_type: TokenStream) -> TokenStream {
|
|
221
|
-
if is_view {
|
|
222
|
-
quote!(ctx: #context_type)
|
|
223
|
-
} else {
|
|
224
|
-
quote!(mut ctx: #context_type)
|
|
225
|
-
}
|
|
202
|
+
Ok(GeneratedInstructions { default_impls, entrypoints, extend_rules })
|
|
226
203
|
}
|
package/src/lib.rs
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
//! with optional default implementations. Each instruction is either **required** (implementor
|
|
6
6
|
//! must provide a handler) or **provided** (a default implementation is available).
|
|
7
7
|
//!
|
|
8
|
-
//! Provided instructions support
|
|
8
|
+
//! Provided instructions support two override levels:
|
|
9
9
|
//!
|
|
10
|
-
//! - **Default**: No custom handler — framework emits accounts struct
|
|
11
|
-
//!
|
|
12
|
-
//! - **
|
|
10
|
+
//! - **Default**: No custom handler defined — the framework emits both the default accounts struct
|
|
11
|
+
//! and an entrypoint that invokes the default handler.
|
|
12
|
+
//! - **Override**: Custom handler defined — the framework still emits the default accounts struct
|
|
13
|
+
//! (making it available for composition or reference), but the entrypoint delegates to the custom
|
|
14
|
+
//! handler using whatever context type it declares.
|
|
13
15
|
//!
|
|
14
16
|
//! See [`spec.md`](../spec.md) for the full specification.
|
|
15
17
|
//!
|
package/src/overrides.rs
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
use itertools::Itertools;
|
|
8
8
|
use proc_macro2::TokenStream;
|
|
9
|
-
use quote::quote;
|
|
9
|
+
use quote::{format_ident, quote};
|
|
10
10
|
use std::collections::BTreeMap;
|
|
11
|
-
use syn::{
|
|
11
|
+
use syn::{Attribute, Item, ItemFn, ItemMod, Type};
|
|
12
12
|
|
|
13
13
|
use crate::InstructionSet;
|
|
14
14
|
|
|
@@ -25,10 +25,6 @@ pub struct OverrideConfig {
|
|
|
25
25
|
pub struct HandlerMeta {
|
|
26
26
|
/// Wrapper module identifier for the relocated handler.
|
|
27
27
|
pub wrapper_mod: syn::Ident,
|
|
28
|
-
/// Original implementor-defined handler function identifier.
|
|
29
|
-
pub fn_ident: syn::Ident,
|
|
30
|
-
/// Inner type `T` extracted from `Context<...T>`.
|
|
31
|
-
pub accounts_type: Type,
|
|
32
28
|
/// The full `Context<...>` type as written (preserves lifetime args).
|
|
33
29
|
pub context_type: Type,
|
|
34
30
|
/// Function-level generics (e.g. `<'info>`) from the override handler.
|
|
@@ -37,8 +33,11 @@ pub struct HandlerMeta {
|
|
|
37
33
|
|
|
38
34
|
/// Collection of custom instruction handlers discovered from a module.
|
|
39
35
|
pub struct InstructionOverrides<Ix: InstructionSet> {
|
|
36
|
+
/// Map of instruction variants to their handler metadata.
|
|
40
37
|
handlers: BTreeMap<Ix, HandlerMeta>,
|
|
38
|
+
/// Token stream containing all generated wrapper module definitions.
|
|
41
39
|
pub wrapper_defs: TokenStream,
|
|
40
|
+
/// Configuration for the override system (attribute name, domain label).
|
|
42
41
|
pub config: OverrideConfig,
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -52,6 +51,7 @@ impl<Ix: InstructionSet> InstructionOverrides<Ix> {
|
|
|
52
51
|
pub fn collect(module: &ItemMod, config: OverrideConfig) -> syn::Result<Self> {
|
|
53
52
|
let mut collected = Self::new(config);
|
|
54
53
|
|
|
54
|
+
// Extract the module content and early return if it's empty.
|
|
55
55
|
let items = match &module.content {
|
|
56
56
|
Some((_, items)) => items,
|
|
57
57
|
None => return Ok(collected),
|
|
@@ -59,6 +59,7 @@ impl<Ix: InstructionSet> InstructionOverrides<Ix> {
|
|
|
59
59
|
|
|
60
60
|
// Walk every function in the module looking for override attributes.
|
|
61
61
|
for item in items {
|
|
62
|
+
// Only process functions that are annotated with the override attribute.
|
|
62
63
|
let Item::Fn(fn_item) = item else { continue };
|
|
63
64
|
let Some(target) = parse_override_target::<Ix>(fn_item, config)? else {
|
|
64
65
|
continue;
|
|
@@ -81,8 +82,8 @@ impl<Ix: InstructionSet> InstructionOverrides<Ix> {
|
|
|
81
82
|
collected.handlers.insert(target, meta);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
// Ensure no
|
|
85
|
-
collected.reject_name_collisions(
|
|
85
|
+
// Ensure no unannotated `fn name()` accidentally shadows a known instruction name.
|
|
86
|
+
collected.reject_name_collisions(items)?;
|
|
86
87
|
Ok(collected)
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -97,10 +98,11 @@ impl<Ix: InstructionSet> InstructionOverrides<Ix> {
|
|
|
97
98
|
return TokenStream::new();
|
|
98
99
|
};
|
|
99
100
|
|
|
101
|
+
// Filter out functions that are overridden.
|
|
100
102
|
let kept: Vec<_> = items
|
|
101
103
|
.iter()
|
|
102
104
|
.filter(|item| match item {
|
|
103
|
-
Item::Fn(fn_item) => !
|
|
105
|
+
Item::Fn(fn_item) => !has_override_attr(fn_item, self.config),
|
|
104
106
|
_ => true,
|
|
105
107
|
})
|
|
106
108
|
.collect();
|
|
@@ -108,26 +110,19 @@ impl<Ix: InstructionSet> InstructionOverrides<Ix> {
|
|
|
108
110
|
quote!(#(#kept)*)
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
/// Checks whether a function ident belongs to an override handler.
|
|
112
|
-
fn is_overridden(&self, fn_ident: &syn::Ident) -> bool {
|
|
113
|
-
self.handlers.values().any(|h| h.fn_ident == *fn_ident)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
113
|
/// Relocates a handler function into an isolated wrapper module
|
|
117
114
|
/// and returns the corresponding [`HandlerMeta`].
|
|
118
115
|
fn wrap_handler(&mut self, target: Ix, fn_item: &ItemFn) -> syn::Result<HandlerMeta> {
|
|
119
|
-
let
|
|
120
|
-
let fn_ident = fn_item.sig.ident.clone();
|
|
116
|
+
let context_type = infer_context_type(fn_item)?;
|
|
121
117
|
let fn_generics = fn_item.sig.generics.clone();
|
|
122
118
|
|
|
123
119
|
// Generate a deterministic module name: `__<attr>__<instruction>_handler`.
|
|
124
|
-
let wrapper_mod =
|
|
125
|
-
|
|
126
|
-
proc_macro2::Span::call_site(),
|
|
127
|
-
);
|
|
120
|
+
let wrapper_mod =
|
|
121
|
+
format_ident!("__{}__{}_handler", self.config.attr_name, target.canonical_name());
|
|
128
122
|
|
|
129
123
|
// Clone the function without the instruction attribute.
|
|
130
|
-
let wrapped_fn =
|
|
124
|
+
let mut wrapped_fn = fn_item.clone();
|
|
125
|
+
wrapped_fn.attrs.retain(|attr| !is_override_attr(attr, self.config));
|
|
131
126
|
|
|
132
127
|
// Emit the wrapper module so the handler lives in its own scope.
|
|
133
128
|
self.wrapper_defs.extend(quote! {
|
|
@@ -137,31 +132,29 @@ impl<Ix: InstructionSet> InstructionOverrides<Ix> {
|
|
|
137
132
|
}
|
|
138
133
|
});
|
|
139
134
|
|
|
140
|
-
Ok(HandlerMeta { wrapper_mod,
|
|
135
|
+
Ok(HandlerMeta { wrapper_mod, context_type, fn_generics })
|
|
141
136
|
}
|
|
142
137
|
|
|
143
138
|
/// Rejects unattributed functions whose name collides with a known instruction.
|
|
144
139
|
///
|
|
145
140
|
/// A bare `fn lz_receive(...)` without `#[oapp_instruction]`
|
|
146
141
|
/// would silently shadow the generated entrypoint — catch that early.
|
|
147
|
-
fn reject_name_collisions(&self,
|
|
148
|
-
let Some((_, items)) = &module.content else {
|
|
149
|
-
return Ok(());
|
|
150
|
-
};
|
|
151
|
-
|
|
142
|
+
fn reject_name_collisions(&self, items: &[Item]) -> syn::Result<()> {
|
|
152
143
|
for &ix in Ix::all() {
|
|
153
144
|
let name = ix.canonical_name();
|
|
154
|
-
let
|
|
155
|
-
Item::Fn(f)
|
|
156
|
-
|
|
145
|
+
let collision = items.iter().find_map(|item| match item {
|
|
146
|
+
Item::Fn(f) if f.sig.ident == name && !has_override_attr(f, self.config) => {
|
|
147
|
+
Some(&f.sig.ident)
|
|
148
|
+
},
|
|
149
|
+
_ => None,
|
|
157
150
|
});
|
|
158
151
|
|
|
159
|
-
if
|
|
160
|
-
return Err(syn::Error::
|
|
161
|
-
|
|
152
|
+
if let Some(ident) = collision {
|
|
153
|
+
return Err(syn::Error::new_spanned(
|
|
154
|
+
ident,
|
|
162
155
|
format!(
|
|
163
156
|
"`fn {name}(...)` conflicts with {domain} instruction `{name}`: \
|
|
164
|
-
|
|
157
|
+
use #[{attr}] to override it",
|
|
165
158
|
domain = self.config.domain,
|
|
166
159
|
attr = self.config.attr_name,
|
|
167
160
|
),
|
|
@@ -183,22 +176,7 @@ fn has_override_attr(fn_item: &ItemFn, cfg: OverrideConfig) -> bool {
|
|
|
183
176
|
|
|
184
177
|
/// Returns `true` if `attr`'s last path segment matches the configured attribute name.
|
|
185
178
|
fn is_override_attr(attr: &Attribute, cfg: OverrideConfig) -> bool {
|
|
186
|
-
attr.path().
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/// Returns a clone of `fn_item` with the override attribute removed.
|
|
190
|
-
fn strip_override_attr(fn_item: &ItemFn, cfg: OverrideConfig) -> syn::Result<ItemFn> {
|
|
191
|
-
let mut cleaned = fn_item.clone();
|
|
192
|
-
let before = cleaned.attrs.len();
|
|
193
|
-
cleaned.attrs.retain(|attr| !is_override_attr(attr, cfg));
|
|
194
|
-
|
|
195
|
-
if cleaned.attrs.len() == before {
|
|
196
|
-
return Err(syn::Error::new_spanned(
|
|
197
|
-
fn_item,
|
|
198
|
-
format!("internal error: expected #[{}] attribute on handler function", cfg.attr_name),
|
|
199
|
-
));
|
|
200
|
-
}
|
|
201
|
-
Ok(cleaned)
|
|
179
|
+
attr.path().is_ident(cfg.attr_name)
|
|
202
180
|
}
|
|
203
181
|
|
|
204
182
|
/// Parses the override target from a function's override attribute.
|
|
@@ -232,11 +210,7 @@ fn parse_override_target<Ix: InstructionSet>(
|
|
|
232
210
|
if !matches!(attr.meta, syn::Meta::Path(_)) {
|
|
233
211
|
return Err(syn::Error::new_spanned(
|
|
234
212
|
attr,
|
|
235
|
-
format!(
|
|
236
|
-
"#[{}] does not accept arguments; the instruction name \
|
|
237
|
-
is inferred from the handler function name",
|
|
238
|
-
cfg.attr_name,
|
|
239
|
-
),
|
|
213
|
+
format!("#[{}] does not accept arguments", cfg.attr_name),
|
|
240
214
|
));
|
|
241
215
|
}
|
|
242
216
|
|
|
@@ -245,45 +219,46 @@ fn parse_override_target<Ix: InstructionSet>(
|
|
|
245
219
|
let ix = Ix::from_ident(fn_ident).ok_or_else(|| {
|
|
246
220
|
syn::Error::new_spanned(
|
|
247
221
|
fn_ident,
|
|
248
|
-
format!("unsupported {} target `{fn_ident}`", cfg.attr_name
|
|
222
|
+
format!("unsupported {} target `{fn_ident}`", cfg.attr_name),
|
|
249
223
|
)
|
|
250
224
|
})?;
|
|
251
225
|
|
|
252
226
|
Ok(Some(ix))
|
|
253
227
|
}
|
|
254
228
|
|
|
255
|
-
/// Extracts the
|
|
229
|
+
/// Extracts the `Context<...>` type from the first `&Context<T>` or `&mut Context<T>` parameter.
|
|
256
230
|
///
|
|
257
|
-
///
|
|
258
|
-
///
|
|
259
|
-
|
|
260
|
-
fn infer_context_type(fn_item: &ItemFn) -> syn::Result<(Type, Type)> {
|
|
231
|
+
/// Override handlers must accept `ctx` by reference — by-value `Context<T>` is rejected.
|
|
232
|
+
/// Returns the `Context<...>` type without the reference wrapper (preserving lifetimes).
|
|
233
|
+
fn infer_context_type(fn_item: &ItemFn) -> syn::Result<Type> {
|
|
261
234
|
let Some(syn::FnArg::Typed(first)) = fn_item.sig.inputs.first() else {
|
|
262
235
|
return Err(syn::Error::new_spanned(
|
|
263
236
|
&fn_item.sig,
|
|
264
|
-
"handler must have at least one parameter of type
|
|
237
|
+
"handler must have at least one parameter of type `&Context<T>` or `&mut Context<T>`",
|
|
265
238
|
));
|
|
266
239
|
};
|
|
267
240
|
|
|
268
|
-
|
|
269
|
-
let
|
|
270
|
-
syn::Error::new_spanned(
|
|
271
|
-
|
|
272
|
-
|
|
241
|
+
// Require `&Context<T>` or `&mut Context<T>` — reject by-value `Context<T>`.
|
|
242
|
+
let Type::Reference(ref_ty) = first.ty.as_ref() else {
|
|
243
|
+
return Err(syn::Error::new_spanned(
|
|
244
|
+
&first.ty,
|
|
245
|
+
"expected `&Context<T>` or `&mut Context<T>` as the first parameter",
|
|
246
|
+
));
|
|
247
|
+
};
|
|
248
|
+
let inner_ty = ref_ty.elem.as_ref();
|
|
249
|
+
|
|
250
|
+
// Validate that the referenced type is `Context<...>`.
|
|
251
|
+
if !is_context_type(inner_ty) {
|
|
252
|
+
return Err(syn::Error::new_spanned(
|
|
253
|
+
&first.ty,
|
|
254
|
+
"expected `&Context<T>` or `&mut Context<T>` as the first parameter",
|
|
255
|
+
));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
Ok(inner_ty.clone())
|
|
273
259
|
}
|
|
274
260
|
|
|
275
|
-
///
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
/// `Context<'_, '_, '_, 'info, T>` form by finding the last type argument.
|
|
279
|
-
fn extract_context_inner_type(ty: &Type) -> Option<Type> {
|
|
280
|
-
let Type::Path(tp) = ty else { return None };
|
|
281
|
-
let seg = tp.path.segments.last().filter(|s| s.ident == "Context")?;
|
|
282
|
-
let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
|
|
283
|
-
return None;
|
|
284
|
-
};
|
|
285
|
-
args.args.iter().rev().find_map(|arg| match arg {
|
|
286
|
-
syn::GenericArgument::Type(inner) => Some(inner.clone()),
|
|
287
|
-
_ => None,
|
|
288
|
-
})
|
|
261
|
+
/// Returns `true` if the type's last path segment is `Context`.
|
|
262
|
+
fn is_context_type(ty: &Type) -> bool {
|
|
263
|
+
matches!(ty, Type::Path(tp) if tp.path.segments.last().is_some_and(|s| s.ident == "Context"))
|
|
289
264
|
}
|