Skip to content

Commit 6ee826b

Browse files
committed
Implement expressive diagnostics for ABI name attribute.
1 parent 644c1c5 commit 6ee826b

File tree

6 files changed

+279
-49
lines changed

6 files changed

+279
-49
lines changed

forc-pkg/src/pkg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1791,7 +1791,7 @@ pub fn compile(
17911791
panic_occurrences: &asm.panic_occurrences,
17921792
abi_with_callpaths: true,
17931793
type_ids_to_full_type_str: HashMap::<String, String>::new(),
1794-
unique_names: HashSet::<String>::new(),
1794+
unique_names: HashMap::new(),
17951795
},
17961796
engines,
17971797
if experimental.new_encoding {

sway-core/src/abi_generation/fuel_abi.rs

Lines changed: 145 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use sway_error::{
99
handler::{ErrorEmitted, Handler},
1010
};
1111
use sway_parse::is_valid_identifier_or_path;
12-
use sway_types::Span;
12+
use sway_types::{Named, Span, Spanned};
1313

1414
use crate::{
1515
ast_elements::type_parameter::GenericTypeParameter,
@@ -20,12 +20,27 @@ use crate::{
2020

2121
use super::abi_str::AbiStrContext;
2222

23+
#[derive(Clone, Debug)]
24+
pub enum AbiNameDiagnosticSpan {
25+
Attribute(Span),
26+
Type(Span),
27+
}
28+
29+
impl AbiNameDiagnosticSpan {
30+
pub fn span(self) -> Span {
31+
match self {
32+
Self::Attribute(span) => span,
33+
Self::Type(span) => span,
34+
}
35+
}
36+
}
37+
2338
pub struct AbiContext<'a> {
2439
pub program: &'a TyProgram,
2540
pub panic_occurrences: &'a PanicOccurrences,
2641
pub abi_with_callpaths: bool,
2742
pub type_ids_to_full_type_str: HashMap<String, String>,
28-
pub unique_names: HashSet<String>,
43+
pub unique_names: HashMap<String, AbiNameDiagnosticSpan>,
2944
}
3045

3146
impl AbiContext<'_> {
@@ -39,6 +54,40 @@ impl AbiContext<'_> {
3954
}
4055
}
4156

57+
pub fn extract_abi_name_inner(span: &Span) -> Option<Span> {
58+
let text = &span.src().text;
59+
let full_attr: &str = &text[span.start()..span.end()];
60+
61+
// Find the "name" key.
62+
let name_key_pos = full_attr.find("name")?;
63+
let after_name = &full_attr[name_key_pos..];
64+
65+
// Find the '=' after "name".
66+
let eq_offset_rel = after_name.find('=')?;
67+
let after_eq = &after_name[eq_offset_rel + 1..];
68+
69+
// Find the opening quote of the literal.
70+
let first_quote_rel = after_eq.find('"')?;
71+
let ident_abs_start = span.start()
72+
+ name_key_pos
73+
+ eq_offset_rel
74+
+ 1 // move past '='
75+
+ first_quote_rel
76+
+ 1; // move past the opening '"'
77+
78+
// Starting at ident_abs_start, locate the closing quote.
79+
let rest_after_ident = &text[ident_abs_start..span.end()];
80+
let second_quote_rel = rest_after_ident.find('"')?;
81+
let ident_abs_end = ident_abs_start + second_quote_rel;
82+
83+
Span::new(
84+
span.src().clone(),
85+
ident_abs_start - 1,
86+
ident_abs_end + 1,
87+
span.source_id().cloned(),
88+
)
89+
}
90+
4291
impl TypeId {
4392
fn get_abi_name_and_span_from_type_id(
4493
engines: &Engines,
@@ -73,7 +122,7 @@ impl TypeId {
73122
}
74123
None => Ok(None),
75124
}
76-
},
125+
}
77126
_ => Ok(None),
78127
}
79128
}
@@ -107,7 +156,7 @@ impl TypeId {
107156

108157
if should_check_name {
109158
let mut has_abi_name_attribute = false;
110-
let (name, span) =
159+
let (name, attribute_span) =
111160
match Self::get_abi_name_and_span_from_type_id(engines, resolved_type_id)? {
112161
Some(res) => {
113162
has_abi_name_attribute = true;
@@ -116,15 +165,42 @@ impl TypeId {
116165
None => (String::new(), Span::dummy()),
117166
};
118167

119-
let inserted = ctx.unique_names.insert(type_str.clone());
168+
let attribute_name_span =
169+
extract_abi_name_inner(&attribute_span).unwrap_or(attribute_span.clone());
170+
171+
let type_span = match *engines.te().get(resolved_type_id) {
172+
TypeInfo::Enum(decl_id) => engines.de().get_enum(&decl_id).name().span(),
173+
TypeInfo::Struct(decl_id) => engines.de().get_struct(&decl_id).name().span(),
174+
_ => unreachable!(),
175+
};
176+
177+
let insert_span = if has_abi_name_attribute {
178+
AbiNameDiagnosticSpan::Attribute(attribute_span.clone())
179+
} else {
180+
AbiNameDiagnosticSpan::Type(type_span.clone())
181+
};
182+
183+
let prev_span_opt = if ctx.unique_names.contains_key(&type_str) {
184+
ctx.unique_names.get(&type_str).cloned()
185+
} else {
186+
ctx.unique_names.insert(type_str.clone(), insert_span)
187+
};
188+
120189
if has_abi_name_attribute {
121190
if name.is_empty() || !is_valid_identifier_or_path(name.as_str()) {
122-
err =
123-
Some(handler.emit_err(CompileError::ABIInvalidName { span: span.clone() }));
191+
err = Some(handler.emit_err(CompileError::ABIInvalidName {
192+
span: attribute_name_span.clone(),
193+
name,
194+
}));
124195
}
125196

126-
if !inserted {
127-
err = Some(handler.emit_err(CompileError::ABIDuplicateName { span }));
197+
if let Some(prev_span) = prev_span_opt {
198+
let is_attribute = matches!(prev_span, AbiNameDiagnosticSpan::Attribute(_));
199+
err = Some(handler.emit_err(CompileError::ABIDuplicateName {
200+
span: attribute_name_span.clone(),
201+
other_span: prev_span.span(),
202+
is_attribute,
203+
}));
128204
}
129205
}
130206
}
@@ -157,6 +233,47 @@ impl TypeId {
157233
}
158234
}
159235

236+
fn insert_unique_type(ctx: &mut AbiContext, name: String, span: Span) {
237+
let _ = ctx
238+
.unique_names
239+
.insert(name, AbiNameDiagnosticSpan::Type(span));
240+
}
241+
242+
fn process_type_name(ctx: &mut AbiContext, engines: &Engines, type_id: TypeId) {
243+
match &*engines.te().get(type_id) {
244+
TypeInfo::Enum(decl_id) => {
245+
let enum_decl = engines.de().get_enum(decl_id);
246+
insert_unique_type(
247+
ctx,
248+
format!("enum {}", enum_decl.name()),
249+
enum_decl.name().span(),
250+
);
251+
}
252+
TypeInfo::Struct(decl_id) => {
253+
let struct_decl = engines.de().get_struct(decl_id);
254+
insert_unique_type(
255+
ctx,
256+
format!("struct {}", struct_decl.name()),
257+
struct_decl.name().span(),
258+
);
259+
}
260+
TypeInfo::Alias { name: _, ty } => process_type_name(ctx, engines, ty.type_id()),
261+
_ => {}
262+
}
263+
}
264+
265+
fn process_type_names_from_function(
266+
ctx: &mut AbiContext,
267+
engines: &Engines,
268+
function: &TyFunctionDecl,
269+
) {
270+
process_type_name(ctx, engines, function.return_type.type_id());
271+
272+
for param in &function.parameters {
273+
process_type_name(ctx, engines, param.type_argument.type_id());
274+
}
275+
}
276+
160277
pub fn generate_program_abi(
161278
handler: &Handler,
162279
ctx: &mut AbiContext,
@@ -167,6 +284,25 @@ pub fn generate_program_abi(
167284
let decl_engine = engines.de();
168285
let metadata_types: &mut Vec<program_abi::TypeMetadataDeclaration> = &mut vec![];
169286
let concrete_types: &mut Vec<program_abi::TypeConcreteDeclaration> = &mut vec![];
287+
288+
match &ctx.program.kind {
289+
TyProgramKind::Contract { abi_entries, .. } => {
290+
abi_entries.iter().for_each(|x| {
291+
let fn_decl = decl_engine.get_function(x);
292+
process_type_names_from_function(ctx, engines, &fn_decl);
293+
});
294+
}
295+
TyProgramKind::Script { main_function, .. } => {
296+
let main_function = decl_engine.get_function(main_function);
297+
process_type_names_from_function(ctx, engines, &main_function);
298+
}
299+
TyProgramKind::Predicate { main_function, .. } => {
300+
let main_function = decl_engine.get_function(main_function);
301+
process_type_names_from_function(ctx, engines, &main_function);
302+
}
303+
TyProgramKind::Library { .. } => {}
304+
};
305+
170306
let mut program_abi = handler.scope(|handler| match &ctx.program.kind {
171307
TyProgramKind::Contract { abi_entries, .. } => {
172308
let functions = abi_entries

sway-error/src/error.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,9 +1045,13 @@ pub enum CompileError {
10451045
#[error("Configurables need a function named \"abi_decode_in_place\" to be in scope.")]
10461046
ConfigurableMissingAbiDecodeInPlace { span: Span },
10471047
#[error("Invalid name found for renamed ABI type.\n")]
1048-
ABIInvalidName { span: Span },
1048+
ABIInvalidName { span: Span, name: String },
10491049
#[error("Duplicated name found for renamed ABI type.\n")]
1050-
ABIDuplicateName { span: Span },
1050+
ABIDuplicateName {
1051+
span: Span,
1052+
other_span: Span,
1053+
is_attribute: bool,
1054+
},
10511055
#[error("Collision detected between two different types.\n Shared hash:{hash}\n First type:{first_type}\n Second type:{second_type}")]
10521056
ABIHashCollision {
10531057
span: Span,
@@ -3087,6 +3091,32 @@ impl ToDiagnostic for CompileError {
30873091
help
30883092
},
30893093
},
3094+
ABIDuplicateName { span, other_span: other, is_attribute } => Diagnostic {
3095+
reason: Some(Reason::new(code(1), "Duplicated name found for renamed ABI type.".into())),
3096+
issue: Issue::error(
3097+
source_engine,
3098+
span.clone(),
3099+
String::new()
3100+
),
3101+
hints: vec![
3102+
Hint::help(
3103+
source_engine,
3104+
other.clone(),
3105+
format!("This is the existing {} with conflicting name", if *is_attribute { "attribute" } else { "type" }),
3106+
)
3107+
],
3108+
help: vec![],
3109+
},
3110+
ABIInvalidName { span, name } => Diagnostic {
3111+
reason: Some(Reason::new(code(1), "Invalid name found for renamed ABI type.".into())),
3112+
issue: Issue::error(
3113+
source_engine,
3114+
span.clone(),
3115+
String::new()
3116+
),
3117+
hints: vec![],
3118+
help: vec![format!("The name must be a valid Sway identifier{}", if name.is_empty() { " and cannot be empty" } else { "" })],
3119+
},
30903120
_ => Diagnostic {
30913121
// TODO: Temporarily we use `self` here to achieve backward compatibility.
30923122
// In general, `self` must not be used. All the values for the formatting

test/src/e2e_vm_tests/test_programs/should_fail/attributes_invalid_abi_names/src/lib.sw

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
contract;
22

3+
mod other;
4+
use other::OtherEnum;
5+
use other::OtherStruct;
6+
37
#[abi_name(name = "SameName")]
48
struct MyStruct {}
59

10+
#[abi_name(name = "MyStruct")]
11+
struct MyStruct0 {}
12+
613
#[abi_name(name = "SameName")]
714
struct MyStruct1 {}
815

@@ -18,28 +25,46 @@ struct MyStruct4 {}
1825
#[abi_name(name = "this::looks::like::a::path")]
1926
struct MyStruct5 {}
2027

28+
#[abi_name(name = "OtherStruct")]
29+
struct MyStruct6 {}
30+
2131
// OK because enums are on a different namespace
2232
#[abi_name(name = "SameName")]
2333
enum MyEnum {
2434
A: ()
2535
}
2636

37+
#[abi_name(name = "OtherEnum")]
38+
enum MyEnum1 {
39+
A: ()
40+
}
41+
2742
abi MyAbi {
43+
fn other_struct() -> OtherStruct;
2844
fn my_struct() -> MyStruct;
45+
fn my_struct0() -> MyStruct0;
2946
fn my_struct1() -> MyStruct1;
3047
fn my_struct2() -> MyStruct2;
3148
fn my_struct3() -> MyStruct3;
3249
fn my_struct4() -> MyStruct4;
3350
fn my_struct5() -> MyStruct5;
51+
fn my_struct6() -> MyStruct6;
52+
fn other_enum() -> OtherEnum;
3453
fn my_enum() -> MyEnum;
54+
fn my_enum1() -> MyEnum1;
3555
}
3656

3757
impl MyAbi for Contract {
58+
fn other_struct() -> OtherStruct { OtherStruct{} }
3859
fn my_struct() -> MyStruct { MyStruct{} }
60+
fn my_struct0() -> MyStruct0 { MyStruct0{} }
3961
fn my_struct1() -> MyStruct1 { MyStruct1{} }
4062
fn my_struct2() -> MyStruct2 { MyStruct2{} }
4163
fn my_struct3() -> MyStruct3 { MyStruct3{} }
4264
fn my_struct4() -> MyStruct4 { MyStruct4{} }
4365
fn my_struct5() -> MyStruct5 { MyStruct5{} }
66+
fn my_struct6() -> MyStruct6 { MyStruct6{} }
67+
fn other_enum() -> OtherEnum { OtherEnum::A }
4468
fn my_enum() -> MyEnum { MyEnum::A }
69+
fn my_enum1() -> MyEnum1 { MyEnum1::A }
4570
}

0 commit comments

Comments
 (0)