Skip to content

Add detailed diagnostics for Rust-style enums #7047

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions sway-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2707,6 +2707,29 @@ impl ToDiagnostic for CompileError {
},
Parse { error } => {
match &error.kind {
ParseErrorKind::MissingColonInEnumTypeField { variant_name, tuple_contents } => Diagnostic {
reason: Some(Reason::new(code(1), "Enum variant declaration is not valid".to_string())),
issue: Issue::error(
source_engine,
error.span.clone(),
format!("`{}` is not a valid enum variant declaration.", error.span.as_str()),
),
hints: vec![
if let Some(tuple_contents) = tuple_contents {
Hint::help(
source_engine,
error.span.clone(),
format!("Did you mean `{}: ({})`?", variant_name, tuple_contents.as_str())
)
} else {
Hint::none()
}
],
help: vec![
"In Sway, enum variants are in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`.".to_string(),
"E.g., `Foo: (), `Bar: u64`, or `Bar: (bool, u32)`.".to_string(),
],
},
ParseErrorKind::UnassignableExpression { erroneous_expression_kind, erroneous_expression_span } => Diagnostic {
reason: Some(Reason::new(code(1), "Expression cannot be assigned to".to_string())),
// A bit of a special handling for parentheses, because they are the only
Expand Down
5 changes: 4 additions & 1 deletion sway-error/src/parser_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ pub enum ParseErrorKind {
#[error("Expected a path type.")]
ExpectedPathType,
#[error("Expected ':'. Enum variants must be in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`. E.g., `Foo: (), or `Bar: (bool, u32)`.")]
MissingColonInEnumTypeField,
MissingColonInEnumTypeField {
variant_name: Ident,
tuple_contents: Option<Span>,
},
#[error("Expected storage key of type U256.")]
ExpectedStorageKeyU256,
}
Expand Down
73 changes: 63 additions & 10 deletions sway-parse/src/item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use sway_ast::{
ItemTypeAlias, ItemUse, Submodule, TraitType, TypeField,
};
use sway_error::parser_error::ParseErrorKind;
use sway_types::ast::Delimiter;
use sway_types::{Ident, Span, Spanned};

mod item_abi;
mod item_configurable;
Expand Down Expand Up @@ -95,16 +97,67 @@ impl Parse for ItemKind {
impl Parse for TypeField {
fn parse(parser: &mut Parser) -> ParseResult<TypeField> {
let visibility = parser.take();
Ok(TypeField {
visibility,
name: parser.parse()?,
colon_token: if parser.peek::<ColonToken>().is_some() {
parser.parse()
} else {
Err(parser.emit_error(ParseErrorKind::MissingColonInEnumTypeField))
}?,
ty: parser.parse()?,
})
let name: Ident = parser.parse()?;
let name_span = name.span();

// Check for the common, valid case `Variant: Type` first.
if parser.peek::<ColonToken>().is_some() {
let colon_token = parser.parse()?;
let ty = parser.parse()?;
return Ok(TypeField {
visibility,
name,
colon_token,
ty,
});
}

// --- Colon was not found after name, proceed with error handling ---

// Case 1: Check for invalid struct-like variant `Variant { ... }`
if let Some((_inner_parser, brace_span)) = parser.enter_delimited(Delimiter::Brace) {
let error_span = Span::join(name_span, &brace_span);
return Err(parser.emit_error_with_span(
ParseErrorKind::MissingColonInEnumTypeField {
variant_name: name,
tuple_contents: None, // Not a tuple issue
},
error_span,
));
}

// Case 2: Check for invalid tuple-like variant `Variant ( ... )` (missing colon)
if let Some((inner_parser, paren_span)) = parser.enter_delimited(Delimiter::Parenthesis) {
let tuple_contents_span = inner_parser.full_span().clone();
let error_span = Span::join(name_span.clone(), &paren_span);
return Err(parser.emit_error_with_span(
ParseErrorKind::MissingColonInEnumTypeField {
variant_name: name,
tuple_contents: Some(tuple_contents_span),
},
error_span,
));
}

// Case 3: Check for unit-like variant `Variant,` or `Variant` (at end)
if parser.is_empty() || parser.peek::<sway_ast::CommaToken>().is_some() {
return Err(parser.emit_error_with_span(
ParseErrorKind::MissingColonInEnumTypeField {
variant_name: name,
tuple_contents: Some(Span::dummy()), // Indicate unit-like
},
name_span,
));
}

// Case 4: Something else follows where a colon was expected
Err(parser.emit_error_with_span(
ParseErrorKind::MissingColonInEnumTypeField {
variant_name: name,
tuple_contents: None,
},
name_span,
))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ enum Enum2 {
G(u32, u32), // Also illegal, but shadowed by previous error
}

enum Enum3 {
F(bool, u32, str[4]), // Illegal
G(u32, u32), // Also illegal, but shadowed by previous error
}

enum Enum4 {
A { x: i32, y: i32 }, // Illegal
Another(u64, bool), // Also illegal, but shadowed by previous error
}

fn main() {
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
category = "fail"

# check: $()error
# check: enum_rust_like/src/main.sw:7:5
# check: enum_rust_like/src/main.sw:7:3
# check: $()Ok, // Illegal
# nextln: $()Expected ':'. Enum variants must be in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`. E.g., `Foo: (), or `Bar: (bool, u32)`.
# nextln: $()`Ok` is not a valid enum variant declaration.
# nextln: $()Did you mean `Ok: ()`?
# check: $()In Sway, enum variants are in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`.
# nextln: $()E.g., `Foo: (), `Bar: u64`, or `Bar: (bool, u32)`.

# check: $()error
# check: enum_rust_like/src/main.sw:12:4
# check: enum_rust_like/src/main.sw:12:3
# check: $()F(u32), // Illegal
# nextln: $()Expected ':'. Enum variants must be in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`. E.g., `Foo: (), or `Bar: (bool, u32)`.
# nextln: $()`F(u32)` is not a valid enum variant declaration.
# nextln: $()Did you mean `F: (u32)`?
# check: $()In Sway, enum variants are in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`.
# nextln: $()E.g., `Foo: (), `Bar: u64`, or `Bar: (bool, u32)`.

# check: $()error
# check: enum_rust_like/src/main.sw:17:3
# check: $()F(bool, u32, str[4]), // Illegal
# nextln: $()`F(bool, u32, str[4])` is not a valid enum variant declaration.
# nextln: $()Did you mean `F: (bool, u32, str[4])`?
# check: $()In Sway, enum variants are in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`.
# nextln: $()E.g., `Foo: (), `Bar: u64`, or `Bar: (bool, u32)`.

# check: $()error
# check: enum_rust_like/src/main.sw:22:3
# check: $()A { x: i32, y: i32 }, // Illegal
# nextln: $()`A { x: i32, y: i32 }` is not a valid enum variant declaration.
# check: $()In Sway, enum variants are in the form `Variant: ()`, `Variant: <type>`, or `Variant: (<type1>, ..., <typeN>)`.
# nextln: $()E.g., `Foo: (), `Bar: u64`, or `Bar: (bool, u32)`.