Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
76 changes: 56 additions & 20 deletions sway-core/src/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2759,17 +2759,42 @@ fn pattern_to_scrutinee(
span,
}
}
Pattern::Struct { path, fields } => Scrutinee::StructScrutinee {
struct_name: path_expr_to_ident(ec, path)?,
fields: {
fields
.into_inner()
.into_iter()
.map(|field| pattern_struct_field_to_struct_scrutinee_field(ec, field))
.collect::<Result<_, _>>()?
},
span,
},
Pattern::Struct { path, fields } => {
let mut errors = Vec::new();
let fields = fields.into_inner();

// Make sure each struct field is declared once
let mut names_of_fields = std::collections::HashSet::new();
fields.clone().into_iter().for_each(|v| {
if let PatternStructField::Field {
field_name,
pattern_opt: _,
} = v
{
if !names_of_fields.insert(field_name.clone()) {
errors.push(ConvertParseTreeError::DuplicateStructField {
name: field_name.clone(),
span: field_name.span(),
});
}
}
});

if let Some(errors) = ec.errors(errors) {
return Err(errors);
}

let scrutinee_fields = fields
.into_iter()
.map(|field| pattern_struct_field_to_struct_scrutinee_field(ec, field))
.collect::<Result<_, _>>()?;

Scrutinee::StructScrutinee {
struct_name: path_expr_to_ident(ec, path)?,
fields: { scrutinee_fields },
span,
}
}
Pattern::Tuple(pat_tuple) => Scrutinee::Tuple {
elems: {
pat_tuple
Expand Down Expand Up @@ -2841,15 +2866,26 @@ fn pattern_struct_field_to_struct_scrutinee_field(
pattern_struct_field: PatternStructField,
) -> Result<StructScrutineeField, ErrorEmitted> {
let span = pattern_struct_field.span();
let struct_scrutinee_field = StructScrutineeField {
field: pattern_struct_field.field_name,
scrutinee: match pattern_struct_field.pattern_opt {
Some((_colon_token, pattern)) => Some(pattern_to_scrutinee(ec, *pattern)?),
None => None,
},
span,
};
Ok(struct_scrutinee_field)
match pattern_struct_field {
PatternStructField::Rest { token } => {
let struct_scrutinee_field = StructScrutineeField::Rest { span: token.span() };
Ok(struct_scrutinee_field)
}
PatternStructField::Field {
field_name,
pattern_opt,
} => {
let struct_scrutinee_field = StructScrutineeField::Field {
field: field_name,
scrutinee: match pattern_opt {
Some((_colon_token, pattern)) => Some(pattern_to_scrutinee(ec, *pattern)?),
None => None,
},
span,
};
Ok(struct_scrutinee_field)
}
}
}

fn assignable_to_expression(
Expand Down
8 changes: 8 additions & 0 deletions sway-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,13 @@ pub enum CompileError {
missing_patterns: String,
span: Span,
},
#[error("Pattern does not mention {}: {}",
if missing_fields.len() == 1 { "field" } else { "fields" },
missing_fields.join(", "))]
MatchStructPatternMissingFields {
missing_fields: Vec<String>,
span: Span,
},
#[error(
"Storage attribute access mismatch. Try giving the surrounding function more access by \
adding \"#[{STORAGE_PURITY_ATTRIBUTE_NAME}({attrs})]\" to the function declaration."
Expand Down Expand Up @@ -1107,6 +1114,7 @@ impl Spanned for CompileError {
StarImportShadowsOtherSymbol { name } => name.span(),
MatchWrongType { span, .. } => span.clone(),
MatchExpressionNonExhaustive { span, .. } => span.clone(),
MatchStructPatternMissingFields { span, .. } => span.clone(),
NotAnEnum { span, .. } => span.clone(),
StorageAccessMismatch { span, .. } => span.clone(),
TraitDeclPureImplImpure { span, .. } => span.clone(),
Expand Down
22 changes: 15 additions & 7 deletions sway-core/src/parse_tree/expression/scrutinee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,15 @@ pub enum Scrutinee {
}

#[derive(Debug, Clone)]
pub struct StructScrutineeField {
pub field: Ident,
pub scrutinee: Option<Scrutinee>,
pub span: Span,
pub enum StructScrutineeField {
Rest {
span: Span,
},
Field {
field: Ident,
scrutinee: Option<Scrutinee>,
span: Span,
},
}

impl Spanned for Scrutinee {
Expand Down Expand Up @@ -122,9 +127,12 @@ impl Scrutinee {
}];
let fields = fields
.iter()
.flat_map(|StructScrutineeField { scrutinee, .. }| match scrutinee {
Some(scrutinee) => scrutinee.gather_approximate_typeinfo_dependencies(),
None => vec![],
.flat_map(|f| match f {
StructScrutineeField::Field {
scrutinee: Some(scrutinee),
..
} => scrutinee.gather_approximate_typeinfo_dependencies(),
_ => vec![],
})
.collect::<Vec<TypeInfo>>();
vec![name, fields].concat()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,20 +140,22 @@ impl Pattern {
..
} => {
let mut new_fields = vec![];
for StructScrutineeField {
field, scrutinee, ..
} in fields.into_iter()
{
let f = match scrutinee {
Some(scrutinee) => check!(
Pattern::from_scrutinee(scrutinee),
return err(warnings, errors),
warnings,
errors
),
None => Pattern::Wildcard,
};
new_fields.push((field.as_str().to_string(), f));
for field in fields.into_iter() {
if let StructScrutineeField::Field {
field, scrutinee, ..
} = field
{
let f = match scrutinee {
Some(scrutinee) => check!(
Pattern::from_scrutinee(scrutinee),
return err(warnings, errors),
warnings,
errors
),
None => Pattern::Wildcard,
};
new_fields.push((field.as_str().to_string(), f));
}
}
Pattern::Struct(StructPattern {
struct_name: struct_name.to_string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use crate::semantic_analysis::declaration::EnforceTypeArguments;
use crate::semantic_analysis::namespace::Namespace;
use crate::semantic_analysis::TypedEnumVariant;
use crate::type_engine::CreateTypeId;
use crate::CompileError;
use crate::{
error::{err, ok},
type_engine::{insert_type, TypeId},
CompileResult, Literal, Scrutinee, TypeArgument, TypeInfo,
};
use crate::{CompileError, StructScrutineeField};

#[derive(Debug, Clone)]
pub(crate) struct TypedScrutinee {
Expand Down Expand Up @@ -96,29 +96,55 @@ impl TypedScrutinee {
);
// type check the fields
let mut typed_fields = vec![];
let mut rest_pattern = None;
for field in fields.into_iter() {
// ensure that the struct definition has this field
let _ = check!(
struct_decl.expect_field(&field.field),
return err(warnings, errors),
warnings,
errors
);
// type check the nested scrutinee
let typed_scrutinee = match field.scrutinee {
None => None,
Some(scrutinee) => Some(check!(
TypedScrutinee::type_check(scrutinee, namespace, self_type),
return err(warnings, errors),
warnings,
errors
)),
};
typed_fields.push(TypedStructScrutineeField {
field: field.field,
scrutinee: typed_scrutinee,
span: field.span,
match field {
StructScrutineeField::Rest { .. } => rest_pattern = Some(field),
StructScrutineeField::Field {
field,
scrutinee,
span,
} => {
// ensure that the struct definition has this field
let _ = check!(
struct_decl.expect_field(&field),
return err(warnings, errors),
warnings,
errors
);
// type check the nested scrutinee
let typed_scrutinee = match scrutinee {
None => None,
Some(scrutinee) => Some(check!(
TypedScrutinee::type_check(scrutinee, namespace, self_type),
return err(warnings, errors),
warnings,
errors
)),
};
typed_fields.push(TypedStructScrutineeField {
field,
scrutinee: typed_scrutinee,
span,
});
}
}
}
// ensure that the pattern uses all fields of the struct unless the rest pattern is present
if (struct_decl.fields.len() != typed_fields.len()) && rest_pattern.is_none() {
let missing_fields = struct_decl
.fields
.iter()
.filter(|f| !typed_fields.iter().any(|tf| f.name == tf.field))
.map(|f| f.name.to_string())
.collect::<Vec<_>>();

errors.push(CompileError::MatchStructPatternMissingFields {
span,
missing_fields,
});

return err(warnings, errors);
}
TypedScrutinee {
variant: TypedScrutineeVariant::StructScrutinee(typed_fields),
Expand Down
2 changes: 2 additions & 0 deletions sway-parse/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ pub enum ParseErrorKind {
UnexpectedTokenAfterAttribute,
#[error("Identifiers cannot begin with a double underscore, as that naming convention is reserved for compiler intrinsics.")]
InvalidDoubleUnderscore,
#[error("Unexpected rest token, must be at the end of pattern.")]
UnexpectedRestPattern,
}

#[derive(Debug, Error, Clone, PartialEq, Hash)]
Expand Down
1 change: 1 addition & 0 deletions sway-parse/src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ define_token!(
[GreaterThan, Equals]
);
define_token!(DotToken, "`.`", [Dot], []);
define_token!(DoubleDotToken, "`..`", [Dot, Dot], [Dot]);
define_token!(BangToken, "`!`", [Bang], [Equals]);
define_token!(PercentToken, "`%`", [Percent], []);
define_token!(AddToken, "`+`", [Add], [Equals]);
Expand Down
44 changes: 37 additions & 7 deletions sway-parse/src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ impl Parse for Pattern {
return Ok(Pattern::Constructor { path, args });
}
if let Some(fields) = Braces::try_parse(parser)? {
let inner_fields: &Punctuated<_, _> = fields.get();
let rest_pattern = inner_fields
.value_separator_pairs
.iter()
.find(|(p, _)| matches!(p, PatternStructField::Rest { token: _ }));

if let Some((rest_pattern, _)) = rest_pattern {
return Err(parser.emit_error_with_span(
ParseErrorKind::UnexpectedRestPattern,
rest_pattern.span(),
));
}

return Ok(Pattern::Struct { path, fields });
}
match path.try_into_ident() {
Expand All @@ -88,22 +101,39 @@ impl Parse for Pattern {
}

#[derive(Clone, Debug)]
pub struct PatternStructField {
pub field_name: Ident,
pub pattern_opt: Option<(ColonToken, Box<Pattern>)>,
pub enum PatternStructField {
Rest {
token: DoubleDotToken,
},
Field {
field_name: Ident,
pattern_opt: Option<(ColonToken, Box<Pattern>)>,
},
}

impl Spanned for PatternStructField {
fn span(&self) -> Span {
match &self.pattern_opt {
Some((_colon_token, pattern)) => Span::join(self.field_name.span(), pattern.span()),
None => self.field_name.span(),
use PatternStructField::*;
match &self {
Rest { token } => token.span(),
Field {
field_name,
pattern_opt,
} => match pattern_opt {
Some((_colon_token, pattern)) => Span::join(field_name.span(), pattern.span()),
None => field_name.span(),
},
}
}
}

impl Parse for PatternStructField {
fn parse(parser: &mut Parser) -> ParseResult<PatternStructField> {
if parser.peek::<DoubleDotToken>().is_some() {
let token = parser.parse()?;
return Ok(PatternStructField::Rest { token });
}

let field_name = parser.parse()?;
let pattern_opt = match parser.take() {
Some(colon_token) => {
Expand All @@ -112,7 +142,7 @@ impl Parse for PatternStructField {
}
None => None,
};
Ok(PatternStructField {
Ok(PatternStructField::Field {
field_name,
pattern_opt,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[package]]
name = 'match_expressions_multiple_rest'
source = 'root'
dependencies = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "match_expressions_multiple_rest"
entry = "main.sw"
implicit-std = false
Loading