Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: Handle arrays with const as length #968

Merged
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -23,6 +23,7 @@ incremented for features.
* lang,ts,ci,cli,docs: update solana toolchain to version 1.8.5([#1133](https://github.com/project-serum/anchor/pull/1133))
* ts: Add optional commitment argument to `fetch` and `fetchMultiple` ([#1171](https://github.com/project-serum/anchor/pull/1171))
* lang: Add `set_inner` method to `Account<'a, T>` to enable easy updates ([#1177](https://github.com/project-serum/anchor/pull/1177))
* lang: Handle arrays with const as length ([#968](https://github.com/project-serum/anchor/pull/968)).

### Breaking

Expand Down
34 changes: 33 additions & 1 deletion lang/syn/src/idl/file.rs
Expand Up @@ -402,9 +402,14 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result<Vec<IdlTypeDefinition>> {
.map(|f: &syn::Field| {
let mut tts = proc_macro2::TokenStream::new();
f.ty.to_tokens(&mut tts);
// Handle array sizes that are constants
let mut tts_string = tts.to_string();
if tts_string.starts_with('[') {
tts_string = resolve_variable_array_length(ctx, tts_string);
}
Ok(IdlField {
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
ty: tts.to_string().parse()?,
ty: tts_string.parse()?,
})
})
.collect::<Result<Vec<IdlField>>>(),
Expand Down Expand Up @@ -455,6 +460,33 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result<Vec<IdlTypeDefinition>> {
.collect()
}

// Replace variable array lengths with values
fn resolve_variable_array_length(ctx: &CrateContext, tts_string: String) -> String {
for constant in ctx.consts() {
if constant.ty.to_token_stream().to_string() == "usize"
&& tts_string.contains(&constant.ident.to_string())
{
// Check for the existence of consts existing elsewhere in the
// crate which have the same name, are usize, and have a
// different value. We can't know which was intended for the
// array size from ctx.
if ctx.consts().any(|c| {
c != constant
&& c.ident == constant.ident
&& c.ty == constant.ty
&& c.expr != constant.expr
}) {
panic!("Crate wide unique name required for array size const.");
}
return tts_string.replace(
&constant.ident.to_string(),
&constant.expr.to_token_stream().to_string(),
);
}
}
tts_string
}

fn to_idl_type(f: &syn::Field) -> IdlType {
let mut tts = proc_macro2::TokenStream::new();
f.ty.to_tokens(&mut tts);
Expand Down
7 changes: 7 additions & 0 deletions tests/misc/programs/misc/src/account.rs
@@ -1,5 +1,7 @@
use anchor_lang::prelude::*;

pub const MAX_SIZE: usize = 10;

#[account]
pub struct Data {
pub udata: u128,
Expand Down Expand Up @@ -41,3 +43,8 @@ pub struct DataWithFilter {
pub struct DataMultidimensionalArray {
pub data: [[u8; 10]; 10],
}

#[account]
pub struct DataConstArraySize {
pub data: [u8; MAX_SIZE],
}
6 changes: 6 additions & 0 deletions tests/misc/programs/misc/src/context.rs
Expand Up @@ -330,3 +330,9 @@ pub struct TestMultidimensionalArray<'info> {
#[account(zero)]
pub data: Account<'info, DataMultidimensionalArray>,
}

#[derive(Accounts)]
pub struct TestConstArraySize<'info> {
#[account(zero)]
pub data: Account<'info, DataConstArraySize>,
}
5 changes: 5 additions & 0 deletions tests/misc/programs/misc/src/lib.rs
Expand Up @@ -87,6 +87,11 @@ pub mod misc {
Ok(())
}

pub fn test_const_array_size(ctx: Context<TestConstArraySize>, data: u8) -> ProgramResult {
ctx.accounts.data.data[0] = data;
Ok(())
}

pub fn test_close(_ctx: Context<TestClose>) -> ProgramResult {
Ok(())
}
Expand Down
18 changes: 18 additions & 0 deletions tests/misc/tests/misc.js
Expand Up @@ -836,6 +836,24 @@ describe("misc", () => {
assert.ok(account.data, 3);
});

it("Can use const for array size", async () => {
const data = anchor.web3.Keypair.generate();
const tx = await program.rpc.testConstArraySize(99, {
accounts: {
data: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [data],
instructions: [
await program.account.dataConstArraySize.createInstruction(data),
],
});
const dataAccount = await program.account.dataConstArraySize.fetch(
data.publicKey
);
assert.deepStrictEqual(dataAccount.data, [99, ...new Array(9).fill(0)]);
});

it("Should include BASE const in IDL", async () => {
assert(
miscIdl.constants.find(
Expand Down