#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod codegen; pub mod parser; #[cfg(feature = "idl-build")] pub mod idl; #[cfg(feature = "hash")] pub mod hash; #[cfg(not(feature = "hash"))] pub(crate) mod hash; use codegen::accounts as accounts_codegen; use codegen::program as program_codegen; use parser::accounts as accounts_parser; use parser::program as program_parser; use proc_macro2::{Span, TokenStream}; use quote::quote; use quote::ToTokens; use std::collections::HashMap; use std::ops::Deref; use syn::ext::IdentExt; use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; use syn::Attribute; use syn::Lit; use syn::{ Expr, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, LitInt, PatType, Token, Type, TypePath, }; #[derive(Debug)] pub struct Program { pub ixs: Vec, pub name: Ident, pub docs: Option>, pub program_mod: ItemMod, pub fallback_fn: Option, } impl Parse for Program { fn parse(input: ParseStream) -> ParseResult { let program_mod = ::parse(input)?; program_parser::parse(program_mod) } } impl From<&Program> for TokenStream { fn from(program: &Program) -> Self { program_codegen::generate(program) } } impl ToTokens for Program { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend::(self.into()); } } #[derive(Debug)] pub struct Ix { pub raw_method: ItemFn, pub ident: Ident, pub docs: Option>, pub cfgs: Vec, pub args: Vec, pub returns: IxReturn, // The ident for the struct deriving Accounts. pub anchor_ident: Ident, // The discriminator based on the `#[interface]` attribute. // TODO: Remove and use `overrides` pub interface_discriminator: Option<[u8; 8]>, /// Overrides coming from the `#[instruction]` attribute pub overrides: Option, } /// Common overrides for the `#[instruction]`, `#[account]` and `#[event]` attributes #[derive(Debug, Default)] pub struct Overrides { /// Override the default 8-byte discriminator pub discriminator: Option, } impl Parse for Overrides { fn parse(input: ParseStream) -> ParseResult { let mut attr = Self::default(); let args = input.parse_terminated::<_, Comma>(NamedArg::parse)?; for arg in args { match arg.name.to_string().as_str() { "discriminator" => { let value = match &arg.value { // Allow `discriminator = 42` Expr::Lit(lit) if matches!(lit.lit, Lit::Int(_)) => quote! { &[#lit] }, // Allow `discriminator = [0, 1, 2, 3]` Expr::Array(arr) => quote! { &#arr }, expr => expr.to_token_stream(), }; attr.discriminator.replace(value) } _ => return Err(ParseError::new(arg.name.span(), "Invalid argument")), }; } Ok(attr) } } struct NamedArg { name: Ident, #[allow(dead_code)] eq_token: Token![=], value: Expr, } impl Parse for NamedArg { fn parse(input: ParseStream) -> ParseResult { Ok(Self { name: input.parse()?, eq_token: input.parse()?, value: input.parse()?, }) } } #[derive(Debug)] pub struct IxArg { pub name: Ident, pub docs: Option>, pub raw_arg: PatType, } #[derive(Debug)] pub struct IxReturn { pub ty: Type, } #[derive(Debug)] pub struct FallbackFn { raw_method: ItemFn, } #[derive(Debug)] pub struct AccountsStruct { // Name of the accounts struct. pub ident: Ident, // Generics + lifetimes on the accounts struct. pub generics: Generics, // Fields on the accounts struct. pub fields: Vec, // Instruction data api expression. instruction_api: Option>, } impl Parse for AccountsStruct { fn parse(input: ParseStream) -> ParseResult { let strct = ::parse(input)?; accounts_parser::parse(&strct) } } impl From<&AccountsStruct> for TokenStream { fn from(accounts: &AccountsStruct) -> Self { accounts_codegen::generate(accounts) } } impl ToTokens for AccountsStruct { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend::(self.into()); } } impl AccountsStruct { pub fn new( strct: ItemStruct, fields: Vec, instruction_api: Option>, ) -> Self { let ident = strct.ident.clone(); let generics = strct.generics; Self { ident, generics, fields, instruction_api, } } // Return value maps instruction name to type. // E.g. if we have `#[instruction(data: u64)]` then returns // { "data": "u64"}. pub fn instruction_args(&self) -> Option> { self.instruction_api.as_ref().map(|instruction_api| { instruction_api .iter() .map(|expr| { let arg = parser::tts_to_string(expr); let components: Vec<&str> = arg.split(" : ").collect(); assert!(components.len() == 2); (components[0].to_string(), components[1].to_string()) }) .collect() }) } pub fn field_names(&self) -> Vec { self.fields .iter() .map(|field| field.ident().to_string()) .collect() } pub fn has_optional(&self) -> bool { for field in &self.fields { if let AccountField::Field(field) = field { if field.is_optional { return true; } } } false } pub fn is_field_optional(&self, field: &T) -> bool { let matching_field = self .fields .iter() .find(|f| *f.ident() == parser::tts_to_string(field)); if let Some(matching_field) = matching_field { matching_field.is_optional() } else { false } } } #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum AccountField { Field(Field), CompositeField(CompositeField), } impl AccountField { fn ident(&self) -> &Ident { match self { AccountField::Field(field) => &field.ident, AccountField::CompositeField(c_field) => &c_field.ident, } } fn is_optional(&self) -> bool { match self { AccountField::Field(field) => field.is_optional, AccountField::CompositeField(_) => false, } } pub fn ty_name(&self) -> Option { let qualified_ty_name = match self { AccountField::Field(field) => match &field.ty { Ty::Account(account) => Some(parser::tts_to_string(&account.account_type_path)), Ty::LazyAccount(account) => Some(parser::tts_to_string(&account.account_type_path)), _ => None, }, AccountField::CompositeField(field) => Some(field.symbol.clone()), }; qualified_ty_name.map(|name| match name.rsplit_once(" :: ") { Some((_prefix, suffix)) => suffix.to_string(), None => name, }) } } #[derive(Debug)] pub struct Field { pub ident: Ident, pub constraints: ConstraintGroup, pub ty: Ty, pub is_optional: bool, /// IDL Doc comment pub docs: Option>, } impl Field { pub fn typed_ident(&self) -> proc_macro2::TokenStream { let name = &self.ident; let ty_decl = self.ty_decl(false); quote! { #name: #ty_decl } } pub fn ty_decl(&self, ignore_option: bool) -> proc_macro2::TokenStream { let account_ty = self.account_ty(); let container_ty = self.container_ty(); let inner_ty = match &self.ty { Ty::AccountInfo => quote! { AccountInfo }, Ty::UncheckedAccount => quote! { UncheckedAccount }, Ty::Signer => quote! { Signer }, Ty::ProgramData => quote! { ProgramData }, Ty::SystemAccount => quote! { SystemAccount }, Ty::Account(AccountTy { boxed, .. }) | Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => { if *boxed { quote! { Box<#container_ty<#account_ty>> } } else { quote! { #container_ty<#account_ty> } } } Ty::Sysvar(ty) => { let account = match ty { SysvarTy::Clock => quote! {Clock}, SysvarTy::Rent => quote! {Rent}, SysvarTy::EpochSchedule => quote! {EpochSchedule}, SysvarTy::Fees => quote! {Fees}, SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes}, SysvarTy::SlotHashes => quote! {SlotHashes}, SysvarTy::SlotHistory => quote! {SlotHistory}, SysvarTy::StakeHistory => quote! {StakeHistory}, SysvarTy::Instructions => quote! {Instructions}, SysvarTy::Rewards => quote! {Rewards}, }; quote! { Sysvar<#account> } } _ => quote! { #container_ty<#account_ty> }, }; if self.is_optional && !ignore_option { quote! { Option<#inner_ty> } } else { quote! { #inner_ty } } } // Ignores optional accounts. Optional account checks and handing should be done prior to this // function being called. pub fn from_account_info( &self, kind: Option<&InitKind>, checked: bool, ) -> proc_macro2::TokenStream { let field = &self.ident; let field_str = field.to_string(); let container_ty = self.container_ty(); let owner_addr = match &kind { None => quote! { __program_id }, Some(InitKind::Program { .. }) => quote! { __program_id }, _ => quote! { &anchor_spl::token::ID }, }; match &self.ty { Ty::AccountInfo => quote! { #field.to_account_info() }, Ty::UncheckedAccount => { quote! { UncheckedAccount::try_from(&#field) } } Ty::Account(AccountTy { boxed, .. }) | Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => { let stream = if checked { quote! { match #container_ty::try_from(&#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } } else { quote! { match #container_ty::try_from_unchecked(&#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } }; if *boxed { quote! { Box::new(#stream) } } else { stream } } Ty::LazyAccount(_) => { if checked { quote! { match #container_ty::try_from(&#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } } else { quote! { match #container_ty::try_from_unchecked(&#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } } } Ty::AccountLoader(_) => { if checked { quote! { match #container_ty::try_from(&#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } } else { quote! { match #container_ty::try_from_unchecked(#owner_addr, &#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } } } _ => { if checked { quote! { match #container_ty::try_from(#owner_addr, &#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } } else { quote! { match #container_ty::try_from_unchecked(#owner_addr, &#field) { Ok(val) => val, Err(e) => return Err(e.with_account_name(#field_str)) } } } } } } pub fn container_ty(&self) -> proc_macro2::TokenStream { match &self.ty { Ty::Account(_) => quote! { anchor_lang::accounts::account::Account }, Ty::LazyAccount(_) => quote! { anchor_lang::accounts::lazy_account::LazyAccount }, Ty::AccountLoader(_) => quote! { anchor_lang::accounts::account_loader::AccountLoader }, Ty::Sysvar(_) => quote! { anchor_lang::accounts::sysvar::Sysvar }, Ty::Program(_) => quote! { anchor_lang::accounts::program::Program }, Ty::Interface(_) => quote! { anchor_lang::accounts::interface::Interface }, Ty::InterfaceAccount(_) => { quote! { anchor_lang::accounts::interface_account::InterfaceAccount } } Ty::AccountInfo => quote! {}, Ty::UncheckedAccount => quote! {}, Ty::Signer => quote! {}, Ty::SystemAccount => quote! {}, Ty::ProgramData => quote! {}, } } // Returns the inner account struct type. pub fn account_ty(&self) -> proc_macro2::TokenStream { match &self.ty { Ty::AccountInfo => quote! { AccountInfo }, Ty::UncheckedAccount => quote! { UncheckedAccount }, Ty::Signer => quote! { Signer }, Ty::SystemAccount => quote! { SystemAccount }, Ty::ProgramData => quote! { ProgramData }, Ty::Account(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::LazyAccount(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::InterfaceAccount(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::AccountLoader(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::Sysvar(ty) => match ty { SysvarTy::Clock => quote! {Clock}, SysvarTy::Rent => quote! {Rent}, SysvarTy::EpochSchedule => quote! {EpochSchedule}, SysvarTy::Fees => quote! {Fees}, SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes}, SysvarTy::SlotHashes => quote! {SlotHashes}, SysvarTy::SlotHistory => quote! {SlotHistory}, SysvarTy::StakeHistory => quote! {StakeHistory}, SysvarTy::Instructions => quote! {Instructions}, SysvarTy::Rewards => quote! {Rewards}, }, Ty::Program(ty) => { let program = &ty.account_type_path; quote! { #program } } Ty::Interface(ty) => { let program = &ty.account_type_path; quote! { #program } } } } } #[derive(Debug)] pub struct CompositeField { pub ident: Ident, pub constraints: ConstraintGroup, pub symbol: String, pub raw_field: syn::Field, /// IDL Doc comment pub docs: Option>, } // A type of an account field. #[derive(Debug, PartialEq, Eq)] pub enum Ty { AccountInfo, UncheckedAccount, AccountLoader(AccountLoaderTy), Sysvar(SysvarTy), Account(AccountTy), LazyAccount(LazyAccountTy), Program(ProgramTy), Interface(InterfaceTy), InterfaceAccount(InterfaceAccountTy), Signer, SystemAccount, ProgramData, } #[derive(Debug, PartialEq, Eq)] pub enum SysvarTy { Clock, Rent, EpochSchedule, Fees, RecentBlockhashes, SlotHashes, SlotHistory, StakeHistory, Instructions, Rewards, } #[derive(Debug, PartialEq, Eq)] pub struct AccountLoaderTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug, PartialEq, Eq)] pub struct AccountTy { // The struct type of the account. pub account_type_path: TypePath, // True if the account has been boxed via `Box`. pub boxed: bool, } #[derive(Debug, PartialEq, Eq)] pub struct LazyAccountTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug, PartialEq, Eq)] pub struct InterfaceAccountTy { // The struct type of the account. pub account_type_path: TypePath, // True if the account has been boxed via `Box`. pub boxed: bool, } #[derive(Debug, PartialEq, Eq)] pub struct ProgramTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug, PartialEq, Eq)] pub struct InterfaceTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug)] pub struct Error { pub name: String, pub raw_enum: ItemEnum, pub ident: Ident, pub codes: Vec, pub args: Option, } #[derive(Debug)] pub struct ErrorArgs { pub offset: LitInt, } impl Parse for ErrorArgs { fn parse(stream: ParseStream) -> ParseResult { let offset_span = stream.span(); let offset = stream.call(Ident::parse_any)?; if offset.to_string().as_str() != "offset" { return Err(ParseError::new(offset_span, "expected keyword offset")); } stream.parse::()?; Ok(ErrorArgs { offset: stream.parse()?, }) } } #[derive(Debug)] pub struct ErrorCode { pub id: u32, pub ident: Ident, pub msg: Option, } // All well formed constraints on a single `Accounts` field. #[derive(Debug, Default, Clone)] pub struct ConstraintGroup { pub init: Option, pub zeroed: Option, pub mutable: Option, pub signer: Option, pub owner: Option, pub rent_exempt: Option, pub seeds: Option, pub executable: Option, pub has_one: Vec, pub raw: Vec, pub close: Option, pub address: Option, pub associated_token: Option, pub token_account: Option, pub mint: Option, pub realloc: Option, } impl ConstraintGroup { pub fn is_zeroed(&self) -> bool { self.zeroed.is_some() } pub fn is_mutable(&self) -> bool { self.mutable.is_some() } pub fn is_signer(&self) -> bool { self.signer.is_some() } pub fn is_close(&self) -> bool { self.close.is_some() } } // A single account constraint *after* merging all tokens into a well formed // constraint. Some constraints like "seeds" are defined by multiple // tokens, so a merging phase is required. #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum Constraint { Init(ConstraintInitGroup), Zeroed(ConstraintZeroed), Mut(ConstraintMut), Signer(ConstraintSigner), HasOne(ConstraintHasOne), Raw(ConstraintRaw), Owner(ConstraintOwner), RentExempt(ConstraintRentExempt), Seeds(ConstraintSeedsGroup), AssociatedToken(ConstraintAssociatedToken), Executable(ConstraintExecutable), Close(ConstraintClose), Address(ConstraintAddress), TokenAccount(ConstraintTokenAccountGroup), Mint(ConstraintTokenMintGroup), Realloc(ConstraintReallocGroup), } // Constraint token is a single keyword in a `#[account()]` attribute. #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum ConstraintToken { Init(Context), Zeroed(Context), Mut(Context), Signer(Context), HasOne(Context), Raw(Context), Owner(Context), RentExempt(Context), Seeds(Context), Executable(Context), Close(Context), Payer(Context), Space(Context), Address(Context), TokenMint(Context), TokenAuthority(Context), TokenTokenProgram(Context), AssociatedTokenMint(Context), AssociatedTokenAuthority(Context), AssociatedTokenTokenProgram(Context), MintAuthority(Context), MintFreezeAuthority(Context), MintDecimals(Context), MintTokenProgram(Context), Bump(Context), ProgramSeed(Context), Realloc(Context), ReallocPayer(Context), ReallocZero(Context), // extensions ExtensionGroupPointerAuthority(Context), ExtensionGroupPointerGroupAddress(Context), ExtensionGroupMemberPointerAuthority(Context), ExtensionGroupMemberPointerMemberAddress( Context, ), ExtensionMetadataPointerAuthority(Context), ExtensionMetadataPointerMetadataAddress( Context, ), ExtensionCloseAuthority(Context), ExtensionTokenHookAuthority(Context), ExtensionTokenHookProgramId(Context), ExtensionPermanentDelegate(Context), } impl Parse for ConstraintToken { fn parse(stream: ParseStream) -> ParseResult { accounts_parser::constraints::parse_token(stream) } } #[derive(Debug, Clone)] pub struct ConstraintInit { pub if_needed: bool, } #[derive(Debug, Clone)] pub struct ConstraintInitIfNeeded {} #[derive(Debug, Clone)] pub struct ConstraintZeroed {} #[derive(Debug, Clone)] pub struct ConstraintMut { pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintReallocGroup { pub payer: Expr, pub space: Expr, pub zero: Expr, } #[derive(Debug, Clone)] pub struct ConstraintRealloc { pub space: Expr, } #[derive(Debug, Clone)] pub struct ConstraintReallocPayer { pub target: Expr, } #[derive(Debug, Clone)] pub struct ConstraintReallocZero { pub zero: Expr, } #[derive(Debug, Clone)] pub struct ConstraintSigner { pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintHasOne { pub join_target: Expr, pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintRaw { pub raw: Expr, pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintOwner { pub owner_address: Expr, pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintAddress { pub address: Expr, pub error: Option, } #[derive(Debug, Clone)] pub enum ConstraintRentExempt { Enforce, Skip, } #[derive(Debug, Clone)] pub struct ConstraintInitGroup { pub if_needed: bool, pub seeds: Option, pub payer: Expr, pub space: Option, pub kind: InitKind, } #[derive(Debug, Clone)] pub struct ConstraintSeedsGroup { pub is_init: bool, pub seeds: Punctuated, pub bump: Option, // None => bump was given without a target. pub program_seed: Option, // None => use the current program's program_id. } #[derive(Debug, Clone)] pub struct ConstraintSeeds { pub seeds: Punctuated, } #[derive(Debug, Clone)] pub struct ConstraintExecutable {} #[derive(Debug, Clone)] pub struct ConstraintPayer { pub target: Expr, } #[derive(Debug, Clone)] pub struct ConstraintSpace { pub space: Expr, } // extension constraints #[derive(Debug, Clone)] pub struct ConstraintExtensionAuthority { pub authority: Expr, } #[derive(Debug, Clone)] pub struct ConstraintExtensionGroupPointerGroupAddress { pub group_address: Expr, } #[derive(Debug, Clone)] pub struct ConstraintExtensionGroupMemberPointerMemberAddress { pub member_address: Expr, } #[derive(Debug, Clone)] pub struct ConstraintExtensionMetadataPointerMetadataAddress { pub metadata_address: Expr, } #[derive(Debug, Clone)] pub struct ConstraintExtensionTokenHookProgramId { pub program_id: Expr, } #[derive(Debug, Clone)] pub struct ConstraintExtensionPermanentDelegate { pub permanent_delegate: Expr, } #[derive(Debug, Clone)] #[allow(clippy::large_enum_variant)] pub enum InitKind { Program { owner: Option, }, Interface { owner: Option, }, // Owner for token and mint represents the authority. Not to be confused // with the owner of the AccountInfo. Token { owner: Expr, mint: Expr, token_program: Option, }, AssociatedToken { owner: Expr, mint: Expr, token_program: Option, }, Mint { owner: Expr, freeze_authority: Option, decimals: Expr, token_program: Option, // extensions group_pointer_authority: Option, group_pointer_group_address: Option, group_member_pointer_authority: Option, group_member_pointer_member_address: Option, metadata_pointer_authority: Option, metadata_pointer_metadata_address: Option, close_authority: Option, permanent_delegate: Option, transfer_hook_authority: Option, transfer_hook_program_id: Option, }, } #[derive(Debug, Clone)] pub struct ConstraintClose { pub sol_dest: Ident, } #[derive(Debug, Clone)] pub struct ConstraintTokenMint { pub mint: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintConfidentialTransferData { pub confidential_transfer_data: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintMetadata { pub token_metadata: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintTokenGroupData { pub token_group_data: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintTokenGroupMemberData { pub token_group_member_data: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintMetadataPointerData { pub metadata_pointer_data: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintGroupPointerData { pub group_pointer_data: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintGroupMemberPointerData { pub group_member_pointer_data: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintCloseAuthority { pub close_authority: Expr, } #[derive(Debug, Clone)] pub struct ConstraintTokenAuthority { pub auth: Expr, } #[derive(Debug, Clone)] pub struct ConstraintTokenProgram { token_program: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintAuthority { pub mint_auth: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintFreezeAuthority { pub mint_freeze_auth: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintDecimals { pub decimals: Expr, } #[derive(Debug, Clone)] pub struct ConstraintTokenBump { pub bump: Option, } #[derive(Debug, Clone)] pub struct ConstraintProgramSeed { pub program_seed: Expr, } #[derive(Debug, Clone)] pub struct ConstraintAssociatedToken { pub wallet: Expr, pub mint: Expr, pub token_program: Option, } #[derive(Debug, Clone)] pub struct ConstraintTokenAccountGroup { pub mint: Option, pub authority: Option, pub token_program: Option, } #[derive(Debug, Clone)] pub struct ConstraintTokenMintGroup { pub decimals: Option, pub mint_authority: Option, pub freeze_authority: Option, pub token_program: Option, pub group_pointer_authority: Option, pub group_pointer_group_address: Option, pub group_member_pointer_authority: Option, pub group_member_pointer_member_address: Option, pub metadata_pointer_authority: Option, pub metadata_pointer_metadata_address: Option, pub close_authority: Option, pub permanent_delegate: Option, pub transfer_hook_authority: Option, pub transfer_hook_program_id: Option, } // Syntaxt context object for preserving metadata about the inner item. #[derive(Debug, Clone)] pub struct Context { span: Span, inner: T, } impl Context { pub fn new(span: Span, inner: T) -> Self { Self { span, inner } } pub fn into_inner(self) -> T { self.inner } } impl Deref for Context { type Target = T; fn deref(&self) -> &Self::Target { &self.inner } } impl Spanned for Context { fn span(&self) -> Span { self.span } }