diff --git a/docs/book/src/reference/attributes.md b/docs/book/src/reference/attributes.md index dad113c7882..0da85c7b6a0 100644 --- a/docs/book/src/reference/attributes.md +++ b/docs/book/src/reference/attributes.md @@ -4,6 +4,7 @@ Attributes are a form of metadata that can additionally instruct Sway compiler o Below is the list of attributes supported by the Sway compiler, ordered alphabetically: +- [ABI Name](#abi-name) - [Allow](#allow) - [Cfg](#cfg) - [Deprecated](#deprecated) @@ -15,6 +16,62 @@ Below is the list of attributes supported by the Sway compiler, ordered alphabet - [Storage](#payable) - [Test](#test) +## ABI Name + +The `#[abi_name]` attribute allows to specify the ABI name for an item. +This means that when a contract ABI JSON file is generated, the name that is output is the one specified +by the attribute. This can be useful to allow renaming items, while allowing for keeping backwards +compatibility at the contract ABI level. + +> **Note**: At the moment, only enum and struct types support the attribute. + +In the example that follows, we originally had a `MyStruct` and `MyEnum` types, which we renamed in Sway: + +```sway +contract; + +#[abi_name(name = "MyStruct")] +struct RenamedMyStruct {} + +#[abi_name(name = "MyEnum")] +enum RenamedMyEnum { + A: () +} + +abi MyAbi { + fn my_struct() -> RenamedMyStruct; + fn my_enum() -> RenamedMyEnum; +} + +impl MyAbi for Contract { + fn my_struct() -> RenamedMyStruct { RenamedMyStruct{} } + fn my_enum() -> RenamedMyEnum { RenamedMyEnum::A } +} +``` + +This generates the following JSON ABI: + +```json +{ + "concreteTypes": [ + { + "concreteTypeId": "215af2bca9e1aa8fec647dab22a0cd36c63ce5ed051a132d51323807e28c0d67", + "metadataTypeId": 1, + "type": "enum MyEnum" + }, + { + "concreteTypeId": "d31db280ac133d726851d8003bd2f06ec2d3fc76a46f1007d13914088fbd0791", + "type": "struct MyStruct" + } + ], + ... +} +``` + +We get the same JSON ABI output both before and after renaming the types, due to attributing them with +`#[abi_name(name = ...)]`, which forces them to be generated with the previous Sway name. +This means consumers of this contract will still get the original names, keeping compatibility at the ABI level. + ## Allow The `#[allow(...)]` attribute disables compiler checks so that certain warnings will go unreported. The following warnings can be disabled: diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 4cb0d55bd23..5722be80140 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -1784,6 +1784,7 @@ pub fn compile( program: typed_program, abi_with_callpaths: true, type_ids_to_full_type_str: HashMap::::new(), + unique_names: HashSet::::new(), }, engines, if experimental.new_encoding { diff --git a/sway-ast/src/attribute.rs b/sway-ast/src/attribute.rs index 98c5f15fdb4..ef805497431 100644 --- a/sway-ast/src/attribute.rs +++ b/sway-ast/src/attribute.rs @@ -52,6 +52,10 @@ pub const ERROR_TYPE_ATTRIBUTE_NAME: &str = "error_type"; pub const ERROR_ATTRIBUTE_NAME: &str = "error"; pub const ERROR_M_ARG_NAME: &str = "m"; +// Abi names. +pub const ABI_NAME_ATTRIBUTE_NAME: &str = "abi_name"; +pub const ABI_NAME_NAME_ARG_NAME: &str = "name"; + pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[ STORAGE_ATTRIBUTE_NAME, DOC_COMMENT_ATTRIBUTE_NAME, @@ -62,6 +66,7 @@ pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[ CFG_ATTRIBUTE_NAME, DEPRECATED_ATTRIBUTE_NAME, FALLBACK_ATTRIBUTE_NAME, + ABI_NAME_ATTRIBUTE_NAME, ]; /// An attribute declaration. Attribute declaration diff --git a/sway-core/src/abi_generation/abi_str.rs b/sway-core/src/abi_generation/abi_str.rs index 59ee467405a..272eca0a607 100644 --- a/sway-core/src/abi_generation/abi_str.rs +++ b/sway-core/src/abi_generation/abi_str.rs @@ -1,6 +1,7 @@ -use sway_types::{integer_bits::IntegerBits, Named}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{integer_bits::IntegerBits, Ident, Named}; -use crate::{language::CallPath, Engines, GenericArgument, TypeId, TypeInfo}; +use crate::{language::CallPath, transform, Engines, GenericArgument, TypeId, TypeInfo}; #[derive(Clone)] pub struct AbiStrContext { @@ -14,14 +15,17 @@ impl TypeId { /// Gives back a string that represents the type, considering what it resolves to pub fn get_abi_type_str( &self, + handler: &Handler, ctx: &AbiStrContext, engines: &Engines, resolved_type_id: TypeId, - ) -> String { + ) -> Result { let type_engine = engines.te(); - let self_abi_str = type_engine.get(*self).abi_str(ctx, engines, true); + let self_abi_str = type_engine + .get(*self) + .abi_str(handler, ctx, engines, true)?; if self.is_generic_parameter(engines, resolved_type_id) { - format!("generic {}", self_abi_str) + Ok(format!("generic {}", self_abi_str)) } else { match ( &*type_engine.get(*self), @@ -30,9 +34,10 @@ impl TypeId { (TypeInfo::Custom { .. }, TypeInfo::Struct { .. }) | (TypeInfo::Custom { .. }, TypeInfo::Enum { .. }) => type_engine .get(resolved_type_id) - .abi_str(ctx, engines, true), + .abi_str(handler, ctx, engines, true), (_, TypeInfo::Alias { ty, .. }) => { - ty.type_id().get_abi_type_str(ctx, engines, ty.type_id()) + ty.type_id() + .get_abi_type_str(handler, ctx, engines, ty.type_id()) } (TypeInfo::Tuple(fields), TypeInfo::Tuple(resolved_fields)) => { assert_eq!(fields.len(), resolved_fields.len()); @@ -40,13 +45,15 @@ impl TypeId { .iter() .map(|f| { if ctx.abi_with_fully_specified_types { - type_engine.get(f.type_id()).abi_str(ctx, engines, false) + type_engine + .get(f.type_id()) + .abi_str(handler, ctx, engines, false) } else { - "_".to_string() + Ok("_".to_string()) } }) - .collect::>(); - format!("({})", field_strs.join(", ")) + .collect::, _>>()?; + Ok(format!("({})", field_strs.join(", "))) } (TypeInfo::Array(_, length), TypeInfo::Array(type_arg, resolved_length)) => { assert_eq!( @@ -56,76 +63,84 @@ impl TypeId { let inner_type = if ctx.abi_with_fully_specified_types { type_engine .get(type_arg.type_id()) - .abi_str(ctx, engines, false) + .abi_str(handler, ctx, engines, false)? } else { "_".to_string() }; - format!("[{}; {:?}]", inner_type, engines.help_out(length.expr())) + Ok(format!( + "[{}; {:?}]", + inner_type, + engines.help_out(length.expr()) + )) } (TypeInfo::Slice(type_arg), TypeInfo::Slice(_)) => { let inner_type = if ctx.abi_with_fully_specified_types { type_engine .get(type_arg.type_id()) - .abi_str(ctx, engines, false) + .abi_str(handler, ctx, engines, false)? } else { "_".to_string() }; - format!("[{}]", inner_type) - } - (TypeInfo::Custom { .. }, _) => { - format!("generic {}", self_abi_str) + Ok(format!("[{}]", inner_type)) } + (TypeInfo::Custom { .. }, _) => Ok(format!("generic {}", self_abi_str)), _ => type_engine .get(resolved_type_id) - .abi_str(ctx, engines, true), + .abi_str(handler, ctx, engines, true), } } } } impl TypeInfo { - pub fn abi_str(&self, ctx: &AbiStrContext, engines: &Engines, is_root: bool) -> String { + pub fn abi_str( + &self, + handler: &Handler, + ctx: &AbiStrContext, + engines: &Engines, + is_root: bool, + ) -> Result { use TypeInfo::*; let decl_engine = engines.de(); match self { - Unknown => "unknown".into(), - Never => "never".into(), - UnknownGeneric { name, .. } => name.to_string(), - Placeholder(_) => "_".to_string(), - TypeParam(param) => format!("typeparam({})", param.name()), - StringSlice => "str".into(), - StringArray(length) => format!("str[{}]", length.val()), - UnsignedInteger(x) => match x { + Unknown => Ok("unknown".into()), + Never => Ok("never".into()), + UnknownGeneric { name, .. } => Ok(name.to_string()), + Placeholder(_) => Ok("_".to_string()), + TypeParam(param) => Ok(format!("typeparam({})", param.name())), + StringSlice => Ok("str".into()), + StringArray(length) => Ok(format!("str[{}]", length.val())), + UnsignedInteger(x) => Ok(match x { IntegerBits::Eight => "u8", IntegerBits::Sixteen => "u16", IntegerBits::ThirtyTwo => "u32", IntegerBits::SixtyFour => "u64", IntegerBits::V256 => "u256", } - .into(), - Boolean => "bool".into(), + .into()), + Boolean => Ok("bool".into()), Custom { qualified_call_path: call_path, .. - } => call_path.call_path.suffix.to_string(), + } => Ok(call_path.call_path.suffix.to_string()), Tuple(fields) => { let field_strs = fields .iter() - .map(|field| field.abi_str(ctx, engines, false)) - .collect::>(); - format!("({})", field_strs.join(", ")) + .map(|field| field.abi_str(handler, ctx, engines, false)) + .collect::, ErrorEmitted>>()?; + Ok(format!("({})", field_strs.join(", "))) } - B256 => "b256".into(), - Numeric => "u64".into(), // u64 is the default - Contract => "contract".into(), - ErrorRecovery(_) => "unknown due to error".into(), + B256 => Ok("b256".into()), + Numeric => Ok("u64".into()), // u64 is the default + Contract => Ok("contract".into()), + ErrorRecovery(_) => Ok("unknown due to error".into()), UntypedEnum(decl_id) => { let decl = engines.pe().get_enum(decl_id); - format!("untyped enum {}", decl.name) + Ok(format!("untyped enum {}", decl.name)) } UntypedStruct(decl_id) => { let decl = engines.pe().get_struct(decl_id); - format!("untyped struct {}", decl.name) + Ok(format!("untyped struct {}", decl.name)) } Enum(decl_ref) => { let decl = decl_engine.get_enum(decl_ref); @@ -134,20 +149,19 @@ impl TypeInfo { { "".into() } else { - format!( - "<{}>", - decl.generic_parameters - .iter() - .map(|p| p.abi_str(engines, ctx, false)) - .collect::>() - .join(",") - ) + let params = decl + .generic_parameters + .iter() + .map(|p| p.abi_str(handler, engines, ctx, false)) + .collect::, _>>()?; + format!("<{}>", params.join(",")) }; - format!( + let abi_call_path = get_abi_call_path(handler, &decl.call_path, &decl.attributes)?; + Ok(format!( "enum {}{}", - call_path_display(ctx, &decl.call_path), + call_path_display(ctx, &abi_call_path), type_params - ) + )) } Struct(decl_ref) => { let decl = decl_engine.get_struct(decl_ref); @@ -156,58 +170,69 @@ impl TypeInfo { { "".into() } else { - format!( - "<{}>", - decl.generic_parameters - .iter() - .map(|p| p.abi_str(engines, ctx, false)) - .collect::>() - .join(",") - ) + let params = decl + .generic_parameters + .iter() + .map(|p| p.abi_str(handler, engines, ctx, false)) + .collect::, _>>()?; + format!("<{}>", params.join(",")) }; - format!( + let abi_call_path = get_abi_call_path(handler, &decl.call_path, &decl.attributes)?; + Ok(format!( "struct {}{}", - call_path_display(ctx, &decl.call_path), + call_path_display(ctx, &abi_call_path), type_params - ) - } - ContractCaller { abi_name, .. } => { - format!("contract caller {abi_name}") - } - Array(elem_ty, length) => { - format!( - "[{}; {:?}]", - elem_ty.abi_str(ctx, engines, false), - engines.help_out(length.expr()) - ) + )) } - RawUntypedPtr => "raw untyped ptr".into(), - RawUntypedSlice => "raw untyped slice".into(), - Ptr(ty) => { - format!("__ptr {}", ty.abi_str(ctx, engines, false)) - } - Slice(ty) => { - format!("__slice {}", ty.abi_str(ctx, engines, false)) - } - Alias { ty, .. } => ty.abi_str(ctx, engines, false), + ContractCaller { abi_name, .. } => Ok(format!("contract caller {abi_name}")), + Array(elem_ty, length) => Ok(format!( + "[{}; {:?}]", + elem_ty.abi_str(handler, ctx, engines, false)?, + engines.help_out(length.expr()) + )), + RawUntypedPtr => Ok("raw untyped ptr".into()), + RawUntypedSlice => Ok("raw untyped slice".into()), + Ptr(ty) => Ok(format!( + "__ptr {}", + ty.abi_str(handler, ctx, engines, false)? + )), + Slice(ty) => Ok(format!( + "__slice {}", + ty.abi_str(handler, ctx, engines, false)? + )), + Alias { ty, .. } => Ok(ty.abi_str(handler, ctx, engines, false)?), TraitType { name, trait_type_id: _, - } => format!("trait type {}", name), + } => Ok(format!("trait type {}", name)), Ref { to_mutable_value, referenced_type, } => { - format!( + Ok(format!( "__ref {}{}", // TODO: (REFERENCES) No references in ABIs according to the RFC. Or we want to have them? if *to_mutable_value { "mut " } else { "" }, - referenced_type.abi_str(ctx, engines, false) - ) + referenced_type.abi_str(handler, ctx, engines, false)? + )) } } } } +fn get_abi_call_path( + handler: &Handler, + call_path: &CallPath, + attributes: &transform::Attributes, +) -> Result { + let mut abi_call_path = call_path.clone(); + if let Some(abi_name_attr) = attributes.abi_name() { + let name = abi_name_attr.args.first().unwrap(); + let ident = Ident::new_no_span(name.get_string(handler, abi_name_attr)?.clone()); + abi_call_path.suffix = ident; + } + Ok(abi_call_path) +} + /// `call_path_display` returns the provided `call_path` without the first prefix in case it is equal to the program name. /// If the program name is `my_program` and the `call_path` is `my_program::MyStruct` then this function returns only `MyStruct`. fn call_path_display(ctx: &AbiStrContext, call_path: &CallPath) -> String { @@ -228,10 +253,16 @@ fn call_path_display(ctx: &AbiStrContext, call_path: &CallPath) -> String { } impl GenericArgument { - pub(self) fn abi_str(&self, ctx: &AbiStrContext, engines: &Engines, is_root: bool) -> String { + pub(self) fn abi_str( + &self, + handler: &Handler, + ctx: &AbiStrContext, + engines: &Engines, + is_root: bool, + ) -> Result { engines .te() .get(self.type_id()) - .abi_str(ctx, engines, is_root) + .abi_str(handler, ctx, engines, is_root) } } diff --git a/sway-core/src/abi_generation/fuel_abi.rs b/sway-core/src/abi_generation/fuel_abi.rs index 55eec7c987d..875ee219e18 100644 --- a/sway-core/src/abi_generation/fuel_abi.rs +++ b/sway-core/src/abi_generation/fuel_abi.rs @@ -3,7 +3,11 @@ use fuel_abi_types::abi::program::{ }; use sha2::{Digest, Sha256}; use std::collections::{HashMap, HashSet}; -use sway_error::handler::{ErrorEmitted, Handler}; +use sway_error::{ + error::CompileError, + handler::{ErrorEmitted, Handler}, +}; +use sway_parse::is_valid_identifier_or_path; use sway_types::Span; use crate::{ @@ -19,6 +23,7 @@ pub struct AbiContext<'a> { pub program: &'a TyProgram, pub abi_with_callpaths: bool, pub type_ids_to_full_type_str: HashMap, + pub unique_names: HashSet, } impl AbiContext<'_> { @@ -33,6 +38,42 @@ impl AbiContext<'_> { } impl TypeId { + fn get_abi_name_and_span_from_type_id( + engines: &Engines, + type_id: TypeId, + ) -> Result, ErrorEmitted> { + let handler = Handler::default(); + match *engines.te().get(type_id) { + TypeInfo::Enum(decl_id) => { + let enum_decl = engines.de().get_enum(&decl_id); + if let Some(abi_name_attr) = enum_decl.attributes.abi_name() { + let name = abi_name_attr + .args + .first() + .unwrap() + .get_string(&handler, abi_name_attr)?; + Ok(Some((name.clone(), abi_name_attr.span.clone()))) + } else { + Ok(None) + } + } + TypeInfo::Struct(decl_id) => { + let struct_decl = engines.de().get_struct(&decl_id); + if let Some(abi_name_attr) = struct_decl.attributes.abi_name() { + let name = abi_name_attr + .args + .first() + .unwrap() + .get_string(&handler, abi_name_attr)?; + Ok(Some((name.clone(), abi_name_attr.span.clone()))) + } else { + Ok(None) + } + } + _ => Ok(None), + } + } + fn get_abi_type_field_and_concrete_id( &self, handler: &Handler, @@ -41,6 +82,7 @@ impl TypeId { resolved_type_id: TypeId, ) -> Result<(String, ConcreteTypeId), ErrorEmitted> { let type_str = self.get_abi_type_str( + handler, &AbiStrContext { program_name: ctx.program.namespace.current_package_name().to_string(), abi_with_callpaths: true, @@ -49,7 +91,40 @@ impl TypeId { }, engines, resolved_type_id, + )?; + + let mut err: Option = None; + + // Right now ABI renaming is only supported for enum and struct types. + let should_check_name = matches!( + *engines.te().get(resolved_type_id), + TypeInfo::Enum(_) | TypeInfo::Struct(_) ); + + if should_check_name { + let mut has_abi_name_attribute = false; + let (name, span) = + match Self::get_abi_name_and_span_from_type_id(engines, resolved_type_id)? { + Some(res) => { + has_abi_name_attribute = true; + res + } + None => (String::new(), Span::dummy()), + }; + + let inserted = ctx.unique_names.insert(type_str.clone()); + if has_abi_name_attribute { + if name.is_empty() || !is_valid_identifier_or_path(name.as_str()) { + err = + Some(handler.emit_err(CompileError::ABIInvalidName { span: span.clone() })); + } + + if !inserted { + err = Some(handler.emit_err(CompileError::ABIDuplicateName { span })); + } + } + } + let mut hasher = Sha256::new(); hasher.update(type_str.clone()); let result = hasher.finalize(); @@ -60,18 +135,21 @@ impl TypeId { .insert(type_id.clone(), type_str.clone()) { if old_type_str != type_str { - return Err( + err = Some( handler.emit_err(sway_error::error::CompileError::ABIHashCollision { span: Span::dummy(), - hash: type_id, + hash: type_id.clone(), first_type: old_type_str, - second_type: type_str, + second_type: type_str.clone(), }), ); } } - Ok((type_str, ConcreteTypeId(type_id))) + match err { + Some(err) => Err(err), + None => Ok((type_str, ConcreteTypeId(type_id))), + } } } @@ -85,7 +163,7 @@ pub fn generate_program_abi( let decl_engine = engines.de(); let metadata_types: &mut Vec = &mut vec![]; let concrete_types: &mut Vec = &mut vec![]; - let mut program_abi = match &ctx.program.kind { + let mut program_abi = handler.scope(|handler| match &ctx.program.kind { TyProgramKind::Contract { abi_entries, .. } => { let functions = abi_entries .iter() @@ -99,24 +177,24 @@ pub fn generate_program_abi( concrete_types, ) }) - .collect::, _>>()?; + .collect::>(); let logged_types = generate_logged_types(handler, ctx, engines, metadata_types, concrete_types)?; let messages_types = generate_messages_types(handler, ctx, engines, metadata_types, concrete_types)?; let configurables = generate_configurables(handler, ctx, engines, metadata_types, concrete_types)?; - program_abi::ProgramABI { + Ok(program_abi::ProgramABI { program_type: "contract".to_string(), spec_version, encoding_version, metadata_types: metadata_types.to_vec(), concrete_types: concrete_types.to_vec(), - functions, + functions: functions.into_iter().collect::, _>>()?, logged_types: Some(logged_types), messages_types: Some(messages_types), configurables: Some(configurables), - } + }) } TyProgramKind::Script { main_function, .. } => { let main_function = decl_engine.get_function(main_function); @@ -133,7 +211,7 @@ pub fn generate_program_abi( generate_messages_types(handler, ctx, engines, metadata_types, concrete_types)?; let configurables = generate_configurables(handler, ctx, engines, metadata_types, concrete_types)?; - program_abi::ProgramABI { + Ok(program_abi::ProgramABI { program_type: "script".to_string(), spec_version, encoding_version, @@ -143,7 +221,7 @@ pub fn generate_program_abi( logged_types: Some(logged_types), messages_types: Some(messages_types), configurables: Some(configurables), - } + }) } TyProgramKind::Predicate { main_function, .. } => { let main_function = decl_engine.get_function(main_function); @@ -160,7 +238,7 @@ pub fn generate_program_abi( generate_messages_types(handler, ctx, engines, metadata_types, concrete_types)?; let configurables = generate_configurables(handler, ctx, engines, metadata_types, concrete_types)?; - program_abi::ProgramABI { + Ok(program_abi::ProgramABI { program_type: "predicate".to_string(), spec_version, encoding_version, @@ -170,7 +248,7 @@ pub fn generate_program_abi( logged_types: Some(logged_types), messages_types: Some(messages_types), configurables: Some(configurables), - } + }) } TyProgramKind::Library { .. } => { let logged_types = @@ -178,7 +256,7 @@ pub fn generate_program_abi( let messages_types = generate_messages_types(handler, ctx, engines, metadata_types, concrete_types)?; - program_abi::ProgramABI { + Ok(program_abi::ProgramABI { program_type: "library".to_string(), spec_version, encoding_version, @@ -188,9 +266,9 @@ pub fn generate_program_abi( logged_types: Some(logged_types), messages_types: Some(messages_types), configurables: None, - } + }) } - }; + })?; standardize_json_abi_types(&mut program_abi); @@ -345,7 +423,7 @@ fn update_json_type_metadata_declaration( } } -/// RUpdates the metadata type IDs used in a `program_abi::TypeConcreteDeclaration` given a HashMap from +/// Updates the metadata type IDs used in a `program_abi::TypeConcreteDeclaration` given a HashMap from /// old to new IDs fn update_json_type_concrete_declaration( type_declaration: &mut program_abi::TypeConcreteDeclaration, @@ -372,7 +450,12 @@ fn generate_concrete_type_declaration( let mut new_metadata_types_to_add = Vec::::new(); let type_metadata_decl = program_abi::TypeMetadataDeclaration { metadata_type_id: MetadataTypeId(type_id.index()), - type_field: type_id.get_abi_type_str(&ctx.to_str_context(), engines, resolved_type_id), + type_field: type_id.get_abi_type_str( + handler, + &ctx.to_str_context(), + engines, + resolved_type_id, + )?, components: type_id.get_abi_type_components( handler, ctx, @@ -400,18 +483,21 @@ fn generate_concrete_type_declaration( } else { None }; - let type_arguments = if type_metadata_decl.type_parameters.is_some() { - type_id.get_abi_type_arguments_as_concrete_type_ids( - handler, - ctx, - engines, - metadata_types, - concrete_types, - resolved_type_id, - )? - } else { - None - }; + let type_arguments = handler.scope(|handler| { + let type_arguments = if type_metadata_decl.type_parameters.is_some() { + type_id.get_abi_type_arguments_as_concrete_type_ids( + handler, + ctx, + engines, + metadata_types, + concrete_types, + resolved_type_id, + )? + } else { + None + }; + Ok(type_arguments) + })?; metadata_types.push(type_metadata_decl); metadata_types.extend(new_metadata_types_to_add); @@ -462,7 +548,12 @@ fn generate_type_metadata_declaration( )?; let type_metadata_decl = program_abi::TypeMetadataDeclaration { metadata_type_id: MetadataTypeId(type_id.index()), - type_field: type_id.get_abi_type_str(&ctx.to_str_context(), engines, resolved_type_id), + type_field: type_id.get_abi_type_str( + handler, + &ctx.to_str_context(), + engines, + resolved_type_id, + )?, components, type_parameters, }; @@ -938,162 +1029,163 @@ impl TypeId { let type_engine = engines.te(); let decl_engine = engines.de(); let resolved_params = resolved_type_id.get_type_parameters(engines); - Ok(match &*type_engine.get(*self) { - TypeInfo::Custom { - type_arguments: Some(type_arguments), - .. - } => (!type_arguments.is_empty()).then_some({ - let resolved_params = resolved_params.unwrap_or_default(); - - for (v, p) in type_arguments.iter().zip(resolved_params.iter()) { - let p = p - .as_type_parameter() - .expect("only works with type parameters"); - generate_type_metadata_declaration( - handler, - ctx, - engines, - metadata_types, - concrete_types, - v.type_id(), - p.type_id, - metadata_types_to_add, - )?; - } - - type_arguments - .iter() - .zip(resolved_params.iter()) - .map(|(arg, p)| { + handler.scope(|handler| { + Ok(match &*type_engine.get(*self) { + TypeInfo::Custom { + type_arguments: Some(type_arguments), + .. + } => (!type_arguments.is_empty()).then_some({ + let resolved_params = resolved_params.unwrap_or_default(); + for (v, p) in type_arguments.iter().zip(resolved_params.iter()) { let p = p .as_type_parameter() .expect("only works with type parameters"); - Ok(program_abi::TypeApplication { - name: "".to_string(), - type_id: program_abi::TypeId::Metadata(MetadataTypeId( - arg.initial_type_id().index(), - )), - type_arguments: arg.initial_type_id().get_abi_type_arguments( - handler, - ctx, - engines, - metadata_types, - concrete_types, - p.type_id, - metadata_types_to_add, - )?, - }) - }) - .collect::, _>>()? - }), - TypeInfo::Enum(decl_ref) => { - let decl = decl_engine.get_enum(decl_ref); + let _ = generate_type_metadata_declaration( + handler, + ctx, + engines, + metadata_types, + concrete_types, + v.type_id(), + p.type_id, + metadata_types_to_add, + ); + } - let mut new_metadata_types_to_add = - Vec::::new(); - for p in decl.generic_parameters.iter() { - let p = p - .as_type_parameter() - .expect("only works with type parameters"); - generate_type_metadata_declaration( - handler, - ctx, - engines, - metadata_types, - concrete_types, - p.type_id, - p.type_id, - &mut new_metadata_types_to_add, - )?; - } + type_arguments + .iter() + .zip(resolved_params.iter()) + .map(|(arg, p)| { + let p = p + .as_type_parameter() + .expect("only works with type parameters"); + Ok(program_abi::TypeApplication { + name: "".to_string(), + type_id: program_abi::TypeId::Metadata(MetadataTypeId( + arg.initial_type_id().index(), + )), + type_arguments: arg.initial_type_id().get_abi_type_arguments( + handler, + ctx, + engines, + metadata_types, + concrete_types, + p.type_id, + metadata_types_to_add, + )?, + }) + }) + .collect::, _>>()? + }), + TypeInfo::Enum(decl_ref) => { + let decl = decl_engine.get_enum(decl_ref); - let type_arguments = decl - .generic_parameters - .iter() - .map(|p| { + let mut new_metadata_types_to_add = + Vec::::new(); + for p in decl.generic_parameters.iter() { let p = p .as_type_parameter() .expect("only works with type parameters"); - Ok(program_abi::TypeApplication { - name: "".to_string(), - type_id: program_abi::TypeId::Metadata(MetadataTypeId( - p.type_id.index(), - )), - type_arguments: p.type_id.get_abi_type_arguments( - handler, - ctx, - engines, - metadata_types, - concrete_types, - p.type_id, - &mut new_metadata_types_to_add, - )?, + generate_type_metadata_declaration( + handler, + ctx, + engines, + metadata_types, + concrete_types, + p.type_id, + p.type_id, + &mut new_metadata_types_to_add, + )?; + } + + let type_arguments = decl + .generic_parameters + .iter() + .map(|p| { + let p = p + .as_type_parameter() + .expect("only works with type parameters"); + Ok(program_abi::TypeApplication { + name: "".to_string(), + type_id: program_abi::TypeId::Metadata(MetadataTypeId( + p.type_id.index(), + )), + type_arguments: p.type_id.get_abi_type_arguments( + handler, + ctx, + engines, + metadata_types, + concrete_types, + p.type_id, + &mut new_metadata_types_to_add, + )?, + }) }) - }) - .collect::, _>>()?; + .collect::, _>>()?; - if type_arguments.is_empty() { - None - } else { - metadata_types_to_add.extend(new_metadata_types_to_add); - Some(type_arguments) + if type_arguments.is_empty() { + None + } else { + metadata_types_to_add.extend(new_metadata_types_to_add); + Some(type_arguments) + } } - } - TypeInfo::Struct(decl_ref) => { - let decl = decl_engine.get_struct(decl_ref); + TypeInfo::Struct(decl_ref) => { + let decl = decl_engine.get_struct(decl_ref); - let mut new_metadata_types_to_add = - Vec::::new(); - for p in decl.generic_parameters.iter() { - let p = p - .as_type_parameter() - .expect("only works with type parameters"); - generate_type_metadata_declaration( - handler, - ctx, - engines, - metadata_types, - concrete_types, - p.type_id, - p.type_id, - &mut new_metadata_types_to_add, - )?; - } - - let type_arguments = decl - .generic_parameters - .iter() - .map(|p| { + let mut new_metadata_types_to_add = + Vec::::new(); + for p in decl.generic_parameters.iter() { let p = p .as_type_parameter() .expect("only works with type parameters"); - Ok(program_abi::TypeApplication { - name: "".to_string(), - type_id: program_abi::TypeId::Metadata(MetadataTypeId( - p.type_id.index(), - )), - type_arguments: p.type_id.get_abi_type_arguments( - handler, - ctx, - engines, - metadata_types, - concrete_types, - p.type_id, - &mut new_metadata_types_to_add, - )?, + generate_type_metadata_declaration( + handler, + ctx, + engines, + metadata_types, + concrete_types, + p.type_id, + p.type_id, + &mut new_metadata_types_to_add, + )?; + } + + let type_arguments = decl + .generic_parameters + .iter() + .map(|p| { + let p = p + .as_type_parameter() + .expect("only works with type parameters"); + Ok(program_abi::TypeApplication { + name: "".to_string(), + type_id: program_abi::TypeId::Metadata(MetadataTypeId( + p.type_id.index(), + )), + type_arguments: p.type_id.get_abi_type_arguments( + handler, + ctx, + engines, + metadata_types, + concrete_types, + p.type_id, + &mut new_metadata_types_to_add, + )?, + }) }) - }) - .collect::, _>>()?; + .collect::, _>>()?; - if type_arguments.is_empty() { - None - } else { - metadata_types_to_add.extend(new_metadata_types_to_add); - Some(type_arguments) + if type_arguments.is_empty() { + None + } else { + metadata_types_to_add.extend(new_metadata_types_to_add); + Some(type_arguments) + } } - } - _ => None, + _ => None, + }) }) } @@ -1196,27 +1288,33 @@ impl TyFunctionDecl { metadata_types: &mut Vec, concrete_types: &mut Vec, ) -> Result { - // Generate the JSON data for the function - Ok(program_abi::ABIFunction { - name: self.name.as_str().to_string(), - inputs: self - .parameters + let inputs = handler.scope(|handler| { + self.parameters .iter() - .map(|x| { - Ok(program_abi::TypeConcreteParameter { - name: x.name.to_string(), - concrete_type_id: generate_concrete_type_declaration( - handler, - ctx, - engines, - metadata_types, - concrete_types, - x.type_argument.initial_type_id(), - x.type_argument.type_id(), - )?, + .map(|param| { + generate_concrete_type_declaration( + handler, + ctx, + engines, + metadata_types, + concrete_types, + param.type_argument.initial_type_id(), + param.type_argument.type_id(), + ) + .map(|concrete_type_id| { + program_abi::TypeConcreteParameter { + name: param.name.to_string(), + concrete_type_id, + } }) }) - .collect::, _>>()?, + .collect::, ErrorEmitted>>() + })?; + + // Generate the JSON data for the function + Ok(program_abi::ABIFunction { + name: self.name.as_str().to_string(), + inputs, output: generate_concrete_type_declaration( handler, ctx, @@ -1263,10 +1361,11 @@ impl GenericTypeParameter { let type_parameter = program_abi::TypeMetadataDeclaration { metadata_type_id: type_id.clone(), type_field: self.initial_type_id.get_abi_type_str( + handler, &ctx.to_str_context(), engines, self.type_id, - ), + )?, components: self.initial_type_id.get_abi_type_components( handler, ctx, diff --git a/sway-core/src/language/ty/expression/expression.rs b/sway-core/src/language/ty/expression/expression.rs index 41a39d0d721..13f51d9b47e 100644 --- a/sway-core/src/language/ty/expression/expression.rs +++ b/sway-core/src/language/ty/expression/expression.rs @@ -376,10 +376,11 @@ impl CollectTypesMetadata for TyExpression { TypeMetadata::get_logged_type_id(exp, ctx.experimental.new_encoding) .map_err(|err| handler.emit_err(err))?; res.push(TypeMetadata::new_logged_type( + handler, ctx.engines, logged_type_id, ctx.program_name.clone(), - )); + )?); // We still need to dive into the expression because it can have additional types to collect. // E.g., `revert some_function_that_returns_error_enum_and_internally_logs_some_types()`; diff --git a/sway-core/src/language/ty/expression/intrinsic_function.rs b/sway-core/src/language/ty/expression/intrinsic_function.rs index 810ff875e96..4279a0d9608 100644 --- a/sway-core/src/language/ty/expression/intrinsic_function.rs +++ b/sway-core/src/language/ty/expression/intrinsic_function.rs @@ -90,10 +90,11 @@ impl CollectTypesMetadata for TyIntrinsicFunctionKind { ) .map_err(|err| handler.emit_err(err))?; let logged_type = TypeMetadata::new_logged_type( + handler, ctx.engines, logged_type_id, ctx.program_name.clone(), - ); + )?; types_metadata.push(logged_type); } Intrinsic::Smo => { diff --git a/sway-core/src/transform/attribute.rs b/sway-core/src/transform/attribute.rs index 29d81651dc1..b073f89cbbf 100644 --- a/sway-core/src/transform/attribute.rs +++ b/sway-core/src/transform/attribute.rs @@ -317,7 +317,7 @@ pub enum ArgsExpectValues { /// /// E.g.: `#[cfg(target = "fuel", experimental_new_encoding = false)]`. Yes, - /// None of the arguments can never have values specified, or the + /// None of the arguments can have values specified, or the /// [Attribute] does not expect any arguments. /// /// E.g.: `#[storage(read, write)]`, `#[fallback]`. @@ -350,6 +350,7 @@ pub enum AttributeKind { Fallback, ErrorType, Error, + AbiName, } /// Denotes if an [ItemTraitItem] belongs to an ABI or to a trait. @@ -380,6 +381,7 @@ impl AttributeKind { FALLBACK_ATTRIBUTE_NAME => AttributeKind::Fallback, ERROR_TYPE_ATTRIBUTE_NAME => AttributeKind::ErrorType, ERROR_ATTRIBUTE_NAME => AttributeKind::Error, + ABI_NAME_ATTRIBUTE_NAME => AttributeKind::AbiName, _ => AttributeKind::Unknown, } } @@ -408,6 +410,7 @@ impl AttributeKind { Fallback => false, ErrorType => false, Error => false, + AbiName => false, } } } @@ -448,6 +451,7 @@ impl Attribute { Fallback => Multiplicity::zero(), ErrorType => Multiplicity::zero(), Error => Multiplicity::exactly(1), + AbiName => Multiplicity::exactly(1), } } @@ -503,6 +507,7 @@ impl Attribute { Fallback => None, ErrorType => None, Error => MustBeIn(vec![ERROR_M_ARG_NAME]), + AbiName => MustBeIn(vec![ABI_NAME_NAME_ARG_NAME]), } } @@ -526,6 +531,7 @@ impl Attribute { ErrorType => No, // `error(msg = "msg")`. Error => Yes, + AbiName => Yes, } } @@ -546,6 +552,7 @@ impl Attribute { Fallback => false, ErrorType => false, Error => false, + AbiName => false, } } @@ -599,6 +606,7 @@ impl Attribute { Fallback => matches!(item_kind, ItemKind::Fn(_)), ErrorType => matches!(item_kind, ItemKind::Enum(_)), Error => false, + AbiName => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)), } } @@ -624,6 +632,7 @@ impl Attribute { Fallback => false, ErrorType => false, Error => struct_or_enum_field == StructOrEnumField::EnumField, + AbiName => false, } } @@ -649,6 +658,7 @@ impl Attribute { Fallback => false, ErrorType => false, Error => false, + AbiName => false, } } @@ -671,6 +681,7 @@ impl Attribute { Fallback => false, ErrorType => false, Error => false, + AbiName => false, } } @@ -692,6 +703,7 @@ impl Attribute { Fallback => false, ErrorType => false, Error => false, + AbiName => false, } } @@ -711,6 +723,7 @@ impl Attribute { Fallback => false, ErrorType => false, Error => false, + AbiName => false, } } @@ -729,6 +742,7 @@ impl Attribute { Fallback => false, ErrorType => false, Error => false, + AbiName => false, } } @@ -780,6 +794,9 @@ impl Attribute { Fallback => vec!["\"fallback\" attribute can only annotate module functions in a contract module."], ErrorType => vec!["\"error_type\" attribute can only annotate enums."], Error => vec!["\"error\" attribute can only annotate enum variants of enums annotated with the \"error_type\" attribute."], + AbiName => vec![ + "\"abi_name\" attribute can only annotate structs and enums.", + ], }; if help.is_empty() && target_friendly_name.starts_with("module kind") { @@ -988,6 +1005,12 @@ impl Attributes { .map(|index| &self.attributes[index]) } + /// Returns the `#[abi_name]` [Attribute], or `None` if the + /// [Attributes] does not contain any `#[abi_name]` attributes. + pub fn abi_name(&self) -> Option<&Attribute> { + self.of_kind(AttributeKind::AbiName).last() + } + /// Returns the `#[test]` [Attribute], or `None` if the /// [Attributes] does not contain any `#[test]` attributes. pub fn test(&self) -> Option<&Attribute> { diff --git a/sway-core/src/type_system/ast_elements/type_parameter.rs b/sway-core/src/type_system/ast_elements/type_parameter.rs index 5ac6008cbc1..3ae3c28ab46 100644 --- a/sway-core/src/type_system/ast_elements/type_parameter.rs +++ b/sway-core/src/type_system/ast_elements/type_parameter.rs @@ -116,9 +116,18 @@ impl TypeParameter { } } - pub fn abi_str(&self, engines: &Engines, ctx: &AbiStrContext, is_root: bool) -> String { + pub fn abi_str( + &self, + handler: &Handler, + engines: &Engines, + ctx: &AbiStrContext, + is_root: bool, + ) -> Result { match self { - TypeParameter::Type(p) => engines.te().get(p.type_id).abi_str(ctx, engines, is_root), + TypeParameter::Type(p) => engines + .te() + .get(p.type_id) + .abi_str(handler, ctx, engines, is_root), TypeParameter::Const(_) => { todo!("Will be implemented by https://github.com/FuelLabs/sway/issues/6860") } diff --git a/sway-core/src/types/collect_types_metadata.rs b/sway-core/src/types/collect_types_metadata.rs index 05d7dfd290c..f881ca9e773 100644 --- a/sway-core/src/types/collect_types_metadata.rs +++ b/sway-core/src/types/collect_types_metadata.rs @@ -108,12 +108,14 @@ impl TypeMetadata { } pub(crate) fn new_logged_type( + handler: &Handler, engines: &Engines, type_id: TypeId, program_name: String, - ) -> Self { - TypeMetadata::LoggedType( + ) -> Result { + Ok(TypeMetadata::LoggedType( LogId::new(type_id.get_abi_type_str( + handler, &AbiStrContext { program_name, abi_with_callpaths: true, @@ -122,9 +124,9 @@ impl TypeMetadata { }, engines, type_id, - )), + )?), type_id, - ) + )) } } diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index 1216224d5d3..410bddcd034 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -1044,6 +1044,10 @@ pub enum CompileError { EncodingUnsupportedType { span: Span }, #[error("Configurables need a function named \"abi_decode_in_place\" to be in scope.")] ConfigurableMissingAbiDecodeInPlace { span: Span }, + #[error("Invalid name found for renamed ABI type.\n")] + ABIInvalidName { span: Span }, + #[error("Duplicated name found for renamed ABI type.\n")] + ABIDuplicateName { span: Span }, #[error("Collision detected between two different types.\n Shared hash:{hash}\n First type:{first_type}\n Second type:{second_type}")] ABIHashCollision { span: Span, @@ -1300,6 +1304,8 @@ impl Spanned for CompileError { CannotBeEvaluatedToConfigurableSizeUnknown { span } => span.clone(), EncodingUnsupportedType { span } => span.clone(), ConfigurableMissingAbiDecodeInPlace { span } => span.clone(), + ABIInvalidName { span, .. } => span.clone(), + ABIDuplicateName { span, .. } => span.clone(), ABIHashCollision { span, .. } => span.clone(), InvalidRangeEndGreaterThanStart { span, .. } => span.clone(), TypeMustBeKnownAtThisPoint { span, .. } => span.clone(), diff --git a/sway-parse/src/lib.rs b/sway-parse/src/lib.rs index b6954c1078e..9ed376220af 100644 --- a/sway-parse/src/lib.rs +++ b/sway-parse/src/lib.rs @@ -24,7 +24,7 @@ pub use crate::{ keywords::RESERVED_KEYWORDS, parse::Parse, parser::Parser, - token::{lex, lex_commented, parse_int_suffix}, + token::{is_valid_identifier_or_path, lex, lex_commented, parse_int_suffix}, }; use sway_ast::{ diff --git a/sway-parse/src/token.rs b/sway-parse/src/token.rs index b67bdc16fc7..a2d9a82df7f 100644 --- a/sway-parse/src/token.rs +++ b/sway-parse/src/token.rs @@ -105,6 +105,29 @@ pub fn lex( lex_commented(handler, src, start, end, &source_id).map(|stream| stream.strip_comments()) } +pub fn is_valid_identifier_or_path(s: &str) -> bool { + // Return false if the string is empty. + if s.is_empty() { + return false; + } + + let mut chars = s.chars(); + + // The first character must be a valid starting character. + let _first = match chars.next() { + Some(c) if c.is_xid_start() || c == '_' => c, + _ => return false, + }; + + // Do not accept a lone underscore as a valid identifier. + if s == "_" { + return false; + } + + // All remaining characters must be valid identifier or path characters. + chars.all(|c| c.is_xid_continue() || c == ':') +} + pub fn lex_commented( handler: &Handler, src: Source, diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/Forc.lock new file mode 100644 index 00000000000..06f765d8ffe --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "attributes_invalid_abi_names" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-F9C5117675511A22" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/Forc.toml new file mode 100644 index 00000000000..5e429734901 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +entry = "lib.sw" +license = "Apache-2.0" +name = "attributes_invalid_abi_names" + +[dependencies] +std = { path = "../../../../../../sway-lib-std" } + diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/snapshot.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/snapshot.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw new file mode 100644 index 00000000000..68c2bf01b1e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw @@ -0,0 +1,45 @@ +contract; + +#[abi_name(name = "SameName")] +struct MyStruct {} + +#[abi_name(name = "SameName")] +struct MyStruct1 {} + +#[abi_name(name = "SameName")] +struct MyStruct2 {} + +#[abi_name(name = "")] +struct MyStruct3 {} + +#[abi_name(name = "this !s n0t an identif1er")] +struct MyStruct4 {} + +#[abi_name(name = "this::looks::like::a::path")] +struct MyStruct5 {} + +// OK because enums are on a different namespace +#[abi_name(name = "SameName")] +enum MyEnum { + A: () +} + +abi MyAbi { + fn my_struct() -> MyStruct; + fn my_struct1() -> MyStruct1; + fn my_struct2() -> MyStruct2; + fn my_struct3() -> MyStruct3; + fn my_struct4() -> MyStruct4; + fn my_struct5() -> MyStruct5; + fn my_enum() -> MyEnum; +} + +impl MyAbi for Contract { + fn my_struct() -> MyStruct { MyStruct{} } + fn my_struct1() -> MyStruct1 { MyStruct1{} } + fn my_struct2() -> MyStruct2 { MyStruct2{} } + fn my_struct3() -> MyStruct3 { MyStruct3{} } + fn my_struct4() -> MyStruct4 { MyStruct4{} } + fn my_struct5() -> MyStruct5 { MyStruct5{} } + fn my_enum() -> MyEnum { MyEnum::A } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/stdout.snap new file mode 100644 index 00000000000..aea371e9efa --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/stdout.snap @@ -0,0 +1,63 @@ +--- +source: test/src/snapshot/mod.rs +--- +> forc build --path test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names +exit status: 1 +output: + Building test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names + Compiling library std (sway-lib-std) + Compiling contract attributes_invalid_abi_names (test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names) +error + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw:6:1 + | +4 | struct MyStruct {} +5 | +6 | #[abi_name(name = "SameName")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Duplicated name found for renamed ABI type. + +7 | struct MyStruct1 {} +8 | + | +____ + +error + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw:9:1 + | + 7 | struct MyStruct1 {} + 8 | + 9 | #[abi_name(name = "SameName")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Duplicated name found for renamed ABI type. + +10 | struct MyStruct2 {} +11 | + | +____ + +error + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw:12:1 + | +10 | struct MyStruct2 {} +11 | +12 | #[abi_name(name = "")] + | ^^^^^^^^^^^^^^^^^^^^^^ Invalid name found for renamed ABI type. + +13 | struct MyStruct3 {} +14 | + | +____ + +error + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw:15:1 + | +13 | struct MyStruct3 {} +14 | +15 | #[abi_name(name = "this !s n0t an identif1er")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid name found for renamed ABI type. + +16 | struct MyStruct4 {} +17 | + | +____ + + Aborting due to 4 errors. +error: Failed to compile attributes_invalid_abi_names diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/abi_name_attr.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/abi_name_attr.sw new file mode 100644 index 00000000000..09d7f5d2a11 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/abi_name_attr.sw @@ -0,0 +1,7 @@ +library; + +#[abi_name(name = "name")] +pub struct Ok { } + +#[abi_name(nam = "name")] +pub struct NotOk { } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/main.sw index 8ba698bdb92..781bfaeb0fb 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/main.sw @@ -15,6 +15,7 @@ mod deprecated_attr; mod fallback_attr; mod error_type_attr; mod error_attr; +mod abi_name_attr; /// Outer doc comment supports multiple. /// Outer doc comment supports multiple. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/stdout.snap index c43e32d61ee..55bdbbaeb93 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/stdout.snap @@ -1,6 +1,5 @@ --- source: test/src/snapshot/mod.rs -assertion_line: 125 --- > forc build --path test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args exit status: 1 @@ -260,5 +259,16 @@ error: Attribute argument is invalid | ____ - Aborting due to 18 errors. +error: Attribute argument is invalid + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args/src/abi_name_attr.sw:6:12 + | +... +6 | #[abi_name(nam = "name")] + | ^^^ "nam" is an invalid argument for attribute "abi_name". + | --- help: Did you mean "name"? + | --- help: The only valid argument is "name". + | +____ + + Aborting due to 19 errors. error: Failed to compile attributes_invalid_args diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/abi_name_attr.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/abi_name_attr.sw new file mode 100644 index 00000000000..6735ee43e2c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/abi_name_attr.sw @@ -0,0 +1,4 @@ +library; + +#[abi_name(name)] +pub struct NotOk { } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/main.sw index fad462d385c..7c04bc8fcee 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/main.sw @@ -16,6 +16,7 @@ mod deprecated_attr; mod fallback_attr; mod error_type_attr; mod error_attr; +mod abi_name_attr; /// Outer doc comment supports multiple. /// Outer doc comment supports multiple. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/stdout.snap index 30a53a944bd..df68b176ff3 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/stdout.snap @@ -1,6 +1,5 @@ --- source: test/src/snapshot/mod.rs -assertion_line: 125 --- > forc build --path test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values exit status: 1 @@ -130,5 +129,15 @@ error: Attribute argument must have a value | ____ - Aborting due to 10 errors. +error: Attribute argument must have a value + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_expect_values/src/abi_name_attr.sw:3:12 + | +... +3 | #[abi_name(name)] + | ^^^^ "name" argument of the attribute "abi_name" must have a value. + | ---- help: To set the value, use the `=` operator: `name = `. + | +____ + + Aborting due to 11 errors. error: Failed to compile attributes_invalid_args_expect_values diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/abi_name_attr.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/abi_name_attr.sw new file mode 100644 index 00000000000..2e454b61656 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/abi_name_attr.sw @@ -0,0 +1,7 @@ +library; + +#[abi_name(name = "name")] +pub struct Ok1 { } + +#[abi_name(name = "name", name = "other name")] +pub struct NotOk1 { } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/main.sw index a10d86f3db5..2f726f6f8db 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/main.sw @@ -16,6 +16,7 @@ mod deprecated_attr; mod fallback_attr; mod error_type_attr; mod error_attr; +mod abi_name_attr; /// Outer doc comment supports multiple. /// Outer doc comment supports multiple. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/stdout.snap index b321589e971..c13f8fcb9fe 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/stdout.snap @@ -1,6 +1,5 @@ --- source: test/src/snapshot/mod.rs -assertion_line: 125 --- > forc build --path test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity exit status: 1 @@ -221,5 +220,14 @@ error: Number of attribute arguments is invalid | ____ - Aborting due to 18 errors. +error: Number of attribute arguments is invalid + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_multiplicity/src/abi_name_attr.sw:6:12 + | +... +6 | #[abi_name(name = "name", name = "other name")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "abi_name" attribute must have exactly one argument, but has two. + | +____ + + Aborting due to 19 errors. error: Failed to compile attributes_invalid_args_multiplicity diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/abi_name_attr.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/abi_name_attr.sw new file mode 100644 index 00000000000..bd8c8cfd8ba --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/abi_name_attr.sw @@ -0,0 +1,4 @@ +library; + +#[abi_name(name = true)] +pub fn not_ok() {} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/main.sw index da9e3cd961b..806489a3d6d 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/main.sw @@ -6,4 +6,5 @@ mod cfg_attr_experimental; mod cfg_attr_program_type; mod cfg_attr_target; mod deprecated_attr; -mod error_attr; \ No newline at end of file +mod error_attr; +mod abi_name_attr; diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/stdout.snap index ad14a6c2a33..426af008257 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/stdout.snap @@ -45,6 +45,16 @@ error: Attribute argument value has a wrong type | ____ +error: Attribute cannot annotate item + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/abi_name_attr.sw:3:3 + | +... +3 | #[abi_name(name = true)] + | ^^^^^^^^ "abi_name" attribute cannot annotate a function declaration. + | + = help: "abi_name" attribute can only annotate structs and enums. +____ + error: Attribute argument value has a wrong type --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_args_values_types/src/error_attr.sw:6:17 | @@ -65,5 +75,5 @@ error: Attribute argument value has a wrong type | ____ - Aborting due to 5 errors. + Aborting due to 6 errors. error: Failed to compile attributes_invalid_args_values_types diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/abi_name_attr.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/abi_name_attr.sw new file mode 100644 index 00000000000..e6df31baa7f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/abi_name_attr.sw @@ -0,0 +1,5 @@ +library; + +#[abi_name] +#[abi_name(name = "name")] +pub struct NotOk { } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/main.sw index 6774ed5344a..e5ffc541a4f 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/main.sw @@ -12,6 +12,7 @@ mod deprecated_attr; mod fallback_attr; mod error_type_attr; mod error_attr; +mod abi_name_attr; /// Outer doc comment supports multiple. /// Outer doc comment supports multiple. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/stdout.snap index bfbdcaae274..cc044b8f380 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/stdout.snap @@ -1,6 +1,5 @@ --- source: test/src/snapshot/mod.rs -assertion_line: 125 --- > forc build --path test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity exit status: 1 @@ -113,5 +112,25 @@ error: Attribute can be applied only once | ____ - Aborting due to 8 errors. +error: Attribute can be applied only once + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/abi_name_attr.sw:4:3 + | +... +3 | #[abi_name] + | -------- info: It is already applied here. +4 | #[abi_name(name = "name")] + | ^^^^^^^^ "abi_name" attribute can be applied only once, but is applied two times. + | +____ + +error: Number of attribute arguments is invalid + --> test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_multiplicity/src/abi_name_attr.sw:3:3 + | +... +3 | #[abi_name] + | ^^^^^^^^ "abi_name" attribute must have exactly one argument, but has none. + | +____ + + Aborting due to 10 errors. error: Failed to compile attributes_invalid_multiplicity diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/Forc.lock new file mode 100644 index 00000000000..075e7e09ef7 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "attributes_abi_name" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-43A72CA5046445DF" diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/Forc.toml new file mode 100644 index 00000000000..a0d624955c5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +entry = "lib.sw" +license = "Apache-2.0" +name = "attributes_abi_name" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } + diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/json_abi_oracle.json new file mode 100644 index 00000000000..582a75fc3ec --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/json_abi_oracle.json @@ -0,0 +1,49 @@ +{ + "concreteTypes": [ + { + "concreteTypeId": "215af2bca9e1aa8fec647dab22a0cd36c63ce5ed051a132d51323807e28c0d67", + "metadataTypeId": 1, + "type": "enum RenamedMyEnum" + }, + { + "concreteTypeId": "d31db280ac133d726851d8003bd2f06ec2d3fc76a46f1007d13914088fbd0791", + "type": "struct RenamedMyStruct" + } + ], + "configurables": [], + "encodingVersion": "1", + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "my_enum", + "output": "215af2bca9e1aa8fec647dab22a0cd36c63ce5ed051a132d51323807e28c0d67" + }, + { + "attributes": null, + "inputs": [], + "name": "my_struct", + "output": "d31db280ac133d726851d8003bd2f06ec2d3fc76a46f1007d13914088fbd0791" + } + ], + "loggedTypes": [], + "messagesTypes": [], + "metadataTypes": [ + { + "metadataTypeId": 0, + "type": "()" + }, + { + "components": [ + { + "name": "A", + "typeId": 0 + } + ], + "metadataTypeId": 1, + "type": "enum RenamedMyEnum" + } + ], + "programType": "contract", + "specVersion": "1" +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/json_abi_oracle_new_encoding.json new file mode 100644 index 00000000000..582a75fc3ec --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/json_abi_oracle_new_encoding.json @@ -0,0 +1,49 @@ +{ + "concreteTypes": [ + { + "concreteTypeId": "215af2bca9e1aa8fec647dab22a0cd36c63ce5ed051a132d51323807e28c0d67", + "metadataTypeId": 1, + "type": "enum RenamedMyEnum" + }, + { + "concreteTypeId": "d31db280ac133d726851d8003bd2f06ec2d3fc76a46f1007d13914088fbd0791", + "type": "struct RenamedMyStruct" + } + ], + "configurables": [], + "encodingVersion": "1", + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "my_enum", + "output": "215af2bca9e1aa8fec647dab22a0cd36c63ce5ed051a132d51323807e28c0d67" + }, + { + "attributes": null, + "inputs": [], + "name": "my_struct", + "output": "d31db280ac133d726851d8003bd2f06ec2d3fc76a46f1007d13914088fbd0791" + } + ], + "loggedTypes": [], + "messagesTypes": [], + "metadataTypes": [ + { + "metadataTypeId": 0, + "type": "()" + }, + { + "components": [ + { + "name": "A", + "typeId": 0 + } + ], + "metadataTypeId": 1, + "type": "enum RenamedMyEnum" + } + ], + "programType": "contract", + "specVersion": "1" +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/src/lib.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/src/lib.sw new file mode 100644 index 00000000000..75ec89e2b0e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/src/lib.sw @@ -0,0 +1,19 @@ +contract; + +#[abi_name(name = "RenamedMyStruct")] +struct MyStruct {} + +#[abi_name(name = "RenamedMyEnum")] +enum MyEnum { + A: () +} + +abi MyAbi { + fn my_struct() -> MyStruct; + fn my_enum() -> MyEnum; +} + +impl MyAbi for Contract { + fn my_struct() -> MyStruct { MyStruct{} } + fn my_enum() -> MyEnum { MyEnum::A } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/test.toml new file mode 100644 index 00000000000..5b08fe8a4da --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_abi_name/test.toml @@ -0,0 +1,3 @@ +category = "compile" +expected_warnings = 0 +validate_abi = true