@layerzerolabs/common-utils-macros-stellar-contracts 0.2.122
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 +21 -0
- package/LICENSE +23 -0
- package/clippy.toml +7 -0
- package/package.json +37 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/auth.rs +95 -0
- package/src/contract_ttl.rs +92 -0
- package/src/error.rs +43 -0
- package/src/lib.rs +585 -0
- package/src/lz_contract.rs +105 -0
- package/src/rbac.rs +90 -0
- package/src/storage.rs +522 -0
- package/src/tests/auth.rs +230 -0
- package/src/tests/contract_ttl.rs +695 -0
- package/src/tests/error.rs +156 -0
- package/src/tests/lz_contract.rs +87 -0
- package/src/tests/mod.rs +11 -0
- package/src/tests/rbac.rs +523 -0
- package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +31 -0
- package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +39 -0
- package/src/tests/snapshots/common_macros__tests__auth__snapshot_only_auth_preserves_function_signature.snap +19 -0
- package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contractimpl_code.snap +77 -0
- package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contracttrait_code.snap +46 -0
- package/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
- package/src/tests/snapshots/common_macros__tests__lz_contract__snapshot_generated_lz_contract_code.snap +51 -0
- package/src/tests/snapshots/common_macros__tests__rbac__snapshot_authorizer_role.snap +21 -0
- package/src/tests/snapshots/common_macros__tests__rbac__snapshot_preserve_function_signature.snap +21 -0
- package/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +10 -0
- package/src/tests/snapshots/common_macros__tests__ttl_extendable__snapshot_generated_ttl_extendable_code.snap +8 -0
- package/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +28 -0
- package/src/tests/storage/extract_fields.rs +87 -0
- package/src/tests/storage/gen_accessor_methods.rs +223 -0
- package/src/tests/storage/gen_args.rs +65 -0
- package/src/tests/storage/gen_enum_variant.rs +78 -0
- package/src/tests/storage/gen_key.rs +108 -0
- package/src/tests/storage/gen_params.rs +105 -0
- package/src/tests/storage/generate_storage.rs +410 -0
- package/src/tests/storage/is_primitive_type.rs +48 -0
- package/src/tests/storage/mod.rs +16 -0
- package/src/tests/storage/parse_default.rs +164 -0
- package/src/tests/storage/parse_name.rs +158 -0
- package/src/tests/storage/parse_no_ttl_extension.rs +124 -0
- package/src/tests/storage/parse_storage_type.rs +174 -0
- package/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +412 -0
- package/src/tests/storage/storage_kind.rs +39 -0
- package/src/tests/storage/test_setup.rs +25 -0
- package/src/tests/storage/validate_attrs.rs +138 -0
- package/src/tests/storage/variant_config.rs +226 -0
- package/src/tests/test_helpers.rs +87 -0
- package/src/tests/ttl_configurable.rs +34 -0
- package/src/tests/ttl_extendable.rs +32 -0
- package/src/tests/upgradeable.rs +169 -0
- package/src/tests/utils.rs +267 -0
- package/src/ttl_configurable.rs +24 -0
- package/src/ttl_extendable.rs +28 -0
- package/src/upgradeable.rs +136 -0
- package/src/utils.rs +56 -0
package/Cargo.toml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @layerzerolabs/common-utils-macros-stellar-contracts. Proc-macro crate; self-contained
|
|
2
|
+
# version/edition/deps so it builds as a path dep and vendors cleanly into consumers.
|
|
3
|
+
[package]
|
|
4
|
+
name = "common-macros"
|
|
5
|
+
version = "0.0.1"
|
|
6
|
+
edition = "2021"
|
|
7
|
+
publish = false
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
proc-macro = true
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
proc-macro2 = "1.0"
|
|
14
|
+
quote = "1.0"
|
|
15
|
+
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
|
16
|
+
heck = "0.5"
|
|
17
|
+
itertools = "0.14"
|
|
18
|
+
|
|
19
|
+
[dev-dependencies]
|
|
20
|
+
insta = "1.44.3"
|
|
21
|
+
prettyplease = "0.2.37"
|
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Copyright (c) 2026 - LayerZero Labs Ltd.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any
|
|
4
|
+
person obtaining a copy of this software and associated
|
|
5
|
+
documentation files (the "Software"), to deal in the
|
|
6
|
+
Software without restriction, including without
|
|
7
|
+
limitation the rights to use, copy, modify, merge,
|
|
8
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software
|
|
10
|
+
is furnished to do so, subject to the following
|
|
11
|
+
conditions:
|
|
12
|
+
The above copyright notice and this permission notice
|
|
13
|
+
shall be included in all copies or substantial portions
|
|
14
|
+
of the Software.
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
16
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
17
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
18
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
19
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
22
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
23
|
+
DEALINGS IN THE SOFTWARE.
|
package/clippy.toml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Clippy configuration for Stellar protocol contracts
|
|
2
|
+
|
|
3
|
+
# Set the too_many_arguments threshold to 11.
|
|
4
|
+
# This aligns with Stellar: contract functions are limited to 10 parameters, plus 1 for 'env'.
|
|
5
|
+
# (Default is 7; raised here to prevent unnecessary clippy warnings on valid contract interfaces.)
|
|
6
|
+
too-many-arguments-threshold = 11
|
|
7
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@layerzerolabs/common-utils-macros-stellar-contracts",
|
|
3
|
+
"version": "0.2.122",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Stellar common-utils procedural macros",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"src",
|
|
10
|
+
"Cargo.toml",
|
|
11
|
+
"rust-toolchain.toml",
|
|
12
|
+
"rustfmt.toml",
|
|
13
|
+
"clippy.toml"
|
|
14
|
+
],
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@layerzerolabs/vm-tooling-stellar": "0.2.122"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"registry": "https://registry.npmjs.org/"
|
|
21
|
+
},
|
|
22
|
+
"externalRepoConfig": {
|
|
23
|
+
"targets": [
|
|
24
|
+
"audit-external",
|
|
25
|
+
"console-pen-test",
|
|
26
|
+
"monorepo-external",
|
|
27
|
+
"onesig-client"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "pnpm exec lz-tool --script \"cargo build\" stellar",
|
|
32
|
+
"clean": "rm -rf ./target ./node_modules",
|
|
33
|
+
"format": "pnpm exec lz-tool --script \"cargo fmt\" stellar",
|
|
34
|
+
"lint": "pnpm exec lz-tool --script \"cargo clippy -- -D warnings\" stellar",
|
|
35
|
+
"test": "pnpm exec lz-tool --script \"cargo nextest run\" stellar"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/rustfmt.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
format_macro_bodies = true
|
|
2
|
+
max_width = 120
|
|
3
|
+
format_macro_matchers = true
|
|
4
|
+
format_strings = true
|
|
5
|
+
imports_granularity = "Crate"
|
|
6
|
+
match_arm_blocks = false
|
|
7
|
+
reorder_impl_items = true
|
|
8
|
+
group_imports = "StdExternalCrate"
|
|
9
|
+
use_field_init_shorthand = true
|
|
10
|
+
use_small_heuristics = "Max"
|
|
11
|
+
wrap_comments = true
|
|
12
|
+
format_code_in_doc_comments = true
|
|
13
|
+
|
|
14
|
+
# most of these are unstable, so we enable them
|
|
15
|
+
unstable_features = true
|
package/src/auth.rs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//! Authorization macros for Stellar contracts.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides macros for implementing authorization patterns:
|
|
4
|
+
//! - `#[ownable]` - Owner-based access control (external owner address)
|
|
5
|
+
//! - `#[multisig]` - MultiSig-based access control (self-owning pattern)
|
|
6
|
+
//! - `#[only_auth]` - Authorization check attribute for functions
|
|
7
|
+
|
|
8
|
+
use crate::utils;
|
|
9
|
+
use proc_macro2::TokenStream;
|
|
10
|
+
use quote::{quote, ToTokens};
|
|
11
|
+
use syn::{parse_quote, ItemFn, ItemStruct};
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Ownable Macro Implementation
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/// Generates the ownable implementation from the `#[ownable]` attribute macro.
|
|
18
|
+
///
|
|
19
|
+
/// This macro implements `OwnableInitializer`, `Auth`, and `Ownable` traits for the contract:
|
|
20
|
+
/// - `OwnableInitializer` provides `init_owner()` for constructor use
|
|
21
|
+
/// - `Auth::authorizer()` returns the stored owner address
|
|
22
|
+
/// - `Ownable` provides ownership management (transfer, accept, renounce)
|
|
23
|
+
pub fn generate_ownable_impl(input: TokenStream) -> TokenStream {
|
|
24
|
+
let item_struct: ItemStruct = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse struct: {}", e));
|
|
25
|
+
let name = &item_struct.ident;
|
|
26
|
+
|
|
27
|
+
quote! {
|
|
28
|
+
#item_struct
|
|
29
|
+
|
|
30
|
+
use utils::{auth::Auth as _, ownable::{Ownable as _, OwnableInitializer as _}};
|
|
31
|
+
|
|
32
|
+
impl utils::ownable::OwnableInitializer for #name {}
|
|
33
|
+
|
|
34
|
+
#[common_macros::contract_impl]
|
|
35
|
+
impl utils::auth::Auth for #name {
|
|
36
|
+
fn authorizer(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
37
|
+
<Self as utils::ownable::Ownable>::owner(env)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
42
|
+
impl utils::ownable::Ownable for #name {}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// MultiSig Macro Implementation
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/// Generates the multisig implementation from the `#[multisig]` attribute macro.
|
|
51
|
+
///
|
|
52
|
+
/// This macro implements both `Auth` and `MultiSig` traits for the contract:
|
|
53
|
+
/// - `Auth::authorizer()` returns the contract's own address (self-owning pattern)
|
|
54
|
+
/// - `MultiSig` provides signature verification and signer management
|
|
55
|
+
pub fn generate_multisig_impl(input: TokenStream) -> TokenStream {
|
|
56
|
+
let item_struct: ItemStruct = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse struct: {}", e));
|
|
57
|
+
let name = &item_struct.ident;
|
|
58
|
+
|
|
59
|
+
quote! {
|
|
60
|
+
#item_struct
|
|
61
|
+
|
|
62
|
+
use utils::{auth::Auth as _, multisig::MultiSig as _};
|
|
63
|
+
|
|
64
|
+
#[common_macros::contract_impl]
|
|
65
|
+
impl utils::auth::Auth for #name {
|
|
66
|
+
fn authorizer(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
67
|
+
Some(env.current_contract_address())
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
72
|
+
impl utils::multisig::MultiSig for #name {}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Only Auth Macro Implementation
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
/// Prepends an auth check to a method using the `Auth` trait.
|
|
81
|
+
///
|
|
82
|
+
/// Works with any contract that implements the `Auth` trait, including both
|
|
83
|
+
/// `Ownable` and `MultiSig` contracts.
|
|
84
|
+
pub fn prepend_only_auth_check(input: TokenStream) -> TokenStream {
|
|
85
|
+
let mut input_fn: ItemFn = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse function: {}", e));
|
|
86
|
+
|
|
87
|
+
let env_param = utils::expect_env_param(&input_fn.sig.inputs);
|
|
88
|
+
|
|
89
|
+
// Get a reference to env (handles both `Env` and `&Env` parameter types)
|
|
90
|
+
let env_ref = env_param.as_ref_tokens();
|
|
91
|
+
|
|
92
|
+
// Insert the auth check at the beginning of the function body
|
|
93
|
+
input_fn.block.stmts.insert(0, parse_quote!(utils::auth::require_auth::<Self>(#env_ref);));
|
|
94
|
+
input_fn.into_token_stream()
|
|
95
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
use crate::utils;
|
|
2
|
+
use proc_macro2::TokenStream;
|
|
3
|
+
use quote::quote;
|
|
4
|
+
use syn::{ImplItem, ItemImpl, ItemTrait, TraitItem, Visibility};
|
|
5
|
+
|
|
6
|
+
/// Generates a `#[soroban_sdk::contractimpl]` with automatic instance TTL extension.
|
|
7
|
+
///
|
|
8
|
+
/// - For `__constructor` methods: injects `ttl_configurable::init_default_ttl_configs(env)`
|
|
9
|
+
/// - For other methods: injects TTL extension logic to extend instance TTL if configured
|
|
10
|
+
pub fn contractimpl_with_ttl(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
11
|
+
let mut impl_block: ItemImpl = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse impl block: {}", e));
|
|
12
|
+
|
|
13
|
+
let is_trait_impl = impl_block.trait_.is_some();
|
|
14
|
+
|
|
15
|
+
for item in &mut impl_block.items {
|
|
16
|
+
let ImplItem::Fn(method) = item else { continue };
|
|
17
|
+
|
|
18
|
+
// For trait impls, process all methods; for inherent impls, only public methods
|
|
19
|
+
if !is_trait_impl && !matches!(method.vis, Visibility::Public(_)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Skip methods without Env parameter
|
|
24
|
+
let Some(env_param) = utils::find_env_param(&method.sig.inputs) else { continue };
|
|
25
|
+
|
|
26
|
+
if method.sig.ident == "__constructor" {
|
|
27
|
+
// Inject default TTL config initialization in constructor
|
|
28
|
+
method.block.stmts.insert(0, init_default_ttl_configs_stmt(&env_param));
|
|
29
|
+
} else {
|
|
30
|
+
// Inject TTL extension at the start of other methods
|
|
31
|
+
method.block.stmts.insert(0, extend_instance_ttl_stmt(&env_param));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let contract_attr = if attr.is_empty() {
|
|
36
|
+
quote! { #[soroban_sdk::contractimpl]}
|
|
37
|
+
} else {
|
|
38
|
+
quote! { #[soroban_sdk::contractimpl(#attr)] }
|
|
39
|
+
};
|
|
40
|
+
quote! {
|
|
41
|
+
#contract_attr
|
|
42
|
+
#impl_block
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Generates a `#[soroban_sdk::contracttrait]` with automatic instance TTL extension.
|
|
47
|
+
///
|
|
48
|
+
/// This macro processes trait definitions and injects TTL extension logic into
|
|
49
|
+
/// default method implementations that have an `Env` parameter.
|
|
50
|
+
pub fn contracttrait_with_ttl(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
51
|
+
let mut trait_def: ItemTrait =
|
|
52
|
+
syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse trait definition: {}", e));
|
|
53
|
+
|
|
54
|
+
for item in &mut trait_def.items {
|
|
55
|
+
let TraitItem::Fn(method) = item else { continue };
|
|
56
|
+
|
|
57
|
+
// Only process methods with default implementations (have a body)
|
|
58
|
+
let Some(ref mut block) = method.default else { continue };
|
|
59
|
+
|
|
60
|
+
// Skip methods without Env parameter
|
|
61
|
+
let Some(env_param) = utils::find_env_param(&method.sig.inputs) else { continue };
|
|
62
|
+
|
|
63
|
+
// Inject TTL extension at the start of the method body
|
|
64
|
+
block.stmts.insert(0, extend_instance_ttl_stmt(&env_param));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let trait_attr = if attr.is_empty() {
|
|
68
|
+
quote! { #[soroban_sdk::contracttrait]}
|
|
69
|
+
} else {
|
|
70
|
+
quote! { #[soroban_sdk::contracttrait(#attr)] }
|
|
71
|
+
};
|
|
72
|
+
quote! {
|
|
73
|
+
#trait_attr
|
|
74
|
+
#trait_def
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Generates a statement that initializes default TTL configs in the constructor.
|
|
79
|
+
fn init_default_ttl_configs_stmt(env_param: &utils::EnvParam<'_>) -> syn::Stmt {
|
|
80
|
+
let env_ref = env_param.as_ref_tokens();
|
|
81
|
+
syn::parse_quote! {
|
|
82
|
+
utils::ttl_configurable::init_default_ttl_configs(#env_ref);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Generates a statement that extends instance TTL if configured.
|
|
87
|
+
fn extend_instance_ttl_stmt(env_param: &utils::EnvParam<'_>) -> syn::Stmt {
|
|
88
|
+
let env_ref = env_param.as_ref_tokens();
|
|
89
|
+
syn::parse_quote! {
|
|
90
|
+
utils::ttl_configurable::extend_instance_ttl(#env_ref);
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/error.rs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
use proc_macro2::TokenStream;
|
|
2
|
+
use quote::quote;
|
|
3
|
+
use syn::{parse_quote, spanned::Spanned, ExprLit, Fields, ItemEnum, Lit, Token};
|
|
4
|
+
|
|
5
|
+
pub fn generate_error(item: TokenStream) -> TokenStream {
|
|
6
|
+
let mut data_enum: ItemEnum = syn::parse2(item).unwrap_or_else(|e| panic!("failed to parse enum: {}", e));
|
|
7
|
+
|
|
8
|
+
// For variants without an explicit discriminant, assign sequential error codes starting at 1
|
|
9
|
+
// (by initializing the counter to 0 and incrementing before assignment).
|
|
10
|
+
let mut current_value = 0;
|
|
11
|
+
for variant in &mut data_enum.variants {
|
|
12
|
+
assert!(matches!(variant.fields, Fields::Unit), "Error enum variants must be unit variants");
|
|
13
|
+
|
|
14
|
+
// Handle variant discriminant assignment
|
|
15
|
+
if let Some((_, disc)) = &variant.discriminant {
|
|
16
|
+
// Explicit discriminant - validate it's greater than previous and update counter
|
|
17
|
+
let val = parse_discriminant_value(disc);
|
|
18
|
+
assert!(val > current_value, "Error enum discriminant must be greater than the previous discriminant");
|
|
19
|
+
current_value = val;
|
|
20
|
+
} else {
|
|
21
|
+
// No discriminant - assign the next sequential value
|
|
22
|
+
current_value += 1;
|
|
23
|
+
variant.discriminant = Some((Token), parse_quote!(#current_value)));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
quote! {
|
|
28
|
+
#[soroban_sdk::contracterror]
|
|
29
|
+
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
|
30
|
+
#[repr(u32)]
|
|
31
|
+
#data_enum
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Parses a discriminant value from a variant, returning the u32 value if valid.
|
|
36
|
+
/// Panics if the discriminant is not a valid integer literal.
|
|
37
|
+
fn parse_discriminant_value(disc: &syn::Expr) -> u32 {
|
|
38
|
+
if let syn::Expr::Lit(ExprLit { lit: Lit::Int(lit_int), .. }) = disc {
|
|
39
|
+
lit_int.base10_parse().expect("Error enum discriminant must be a valid u32 integer")
|
|
40
|
+
} else {
|
|
41
|
+
panic!("Error enum discriminant must be an integer literal")
|
|
42
|
+
}
|
|
43
|
+
}
|