Skip to content
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

fix(experimental): Allow shadowing in match patterns #7484

Merged
merged 2 commits into from
Feb 21, 2025
Merged
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
65 changes: 48 additions & 17 deletions compiler/noirc_frontend/src/elaborator/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use crate::{
ast::{
EnumVariant, Expression, ExpressionKind, FunctionKind, Literal, NoirEnumeration,
EnumVariant, Expression, ExpressionKind, FunctionKind, Ident, Literal, NoirEnumeration,
StatementKind, UnresolvedType, Visibility,
},
elaborator::path_resolution::PathResolutionItem,
Expand Down Expand Up @@ -271,7 +271,8 @@

let rows = vecmap(rules, |(pattern, branch)| {
self.push_scope();
let pattern = self.expression_to_pattern(pattern, &expected_pattern_type);
let pattern =
self.expression_to_pattern(pattern, &expected_pattern_type, &mut Vec::new());
let columns = vec![Column::new(variable_to_match, pattern)];

let guard = None;
Expand All @@ -293,7 +294,12 @@
}

/// Convert an expression into a Pattern, defining any variables within.
fn expression_to_pattern(&mut self, expression: Expression, expected_type: &Type) -> Pattern {
fn expression_to_pattern(
&mut self,
expression: Expression,
expected_type: &Type,
variables_defined: &mut Vec<Ident>,
) -> Pattern {
let expr_location = expression.type_location();
let unify_with_expected_type = |this: &mut Self, actual| {
this.unify(actual, expected_type, expr_location.file, || {
Expand Down Expand Up @@ -333,13 +339,25 @@
Vec::new(),
expected_type,
location,
variables_defined,
),
Err(_) if path_len == 1 => {
// Define the variable
let kind = DefinitionKind::Local(None);
// TODO: `allow_shadowing` is false while I'm too lazy to add a check that we
// don't define the same name multiple times in one pattern.
let id = self.add_variable_decl(last_ident, false, false, true, kind).id;

if let Some(existing) =
variables_defined.iter().find(|elem| *elem == &last_ident)
{
let error = ResolverError::VariableAlreadyDefinedInPattern {
existing: existing.clone(),
new_span: last_ident.span(),
};
self.push_err(error, self.file);
} else {
variables_defined.push(last_ident.clone());
}

let id = self.add_variable_decl(last_ident, false, true, true, kind).id;
self.interner.push_definition_type(id, expected_type.clone());
Pattern::Binding(id)
}
Expand All @@ -352,9 +370,12 @@
}
}
}
ExpressionKind::Call(call) => {
self.expression_to_constructor(*call.func, call.arguments, expected_type)
}
ExpressionKind::Call(call) => self.expression_to_constructor(
*call.func,
call.arguments,
expected_type,
variables_defined,
),
ExpressionKind::Constructor(_) => todo!("handle constructors"),
ExpressionKind::Tuple(fields) => {
let field_types = vecmap(0..fields.len(), |_| self.interner.next_type_variable());
Expand All @@ -363,21 +384,23 @@

let fields = vecmap(fields.into_iter().enumerate(), |(i, field)| {
let expected = field_types.get(i).unwrap_or(&Type::Error);
self.expression_to_pattern(field, expected)
self.expression_to_pattern(field, expected, variables_defined)
});

Pattern::Constructor(Constructor::Tuple(field_types.clone()), fields)
}

ExpressionKind::Parenthesized(expr) => self.expression_to_pattern(*expr, expected_type),
ExpressionKind::Parenthesized(expr) => {
self.expression_to_pattern(*expr, expected_type, variables_defined)
}
ExpressionKind::Interned(id) => {
let kind = self.interner.get_expression_kind(id);
let expr = Expression::new(kind.clone(), expression.location);
self.expression_to_pattern(expr, expected_type)
self.expression_to_pattern(expr, expected_type, variables_defined)
}
ExpressionKind::InternedStatement(id) => {
if let StatementKind::Expression(expr) = self.interner.get_statement_kind(id) {
self.expression_to_pattern(expr.clone(), expected_type)
self.expression_to_pattern(expr.clone(), expected_type, variables_defined)
} else {
panic!("Invalid expr kind {expression}")
}
Expand Down Expand Up @@ -413,6 +436,7 @@
name: Expression,
args: Vec<Expression>,
expected_type: &Type,
variables_defined: &mut Vec<Ident>,
) -> Pattern {
match name.kind {
ExpressionKind::Variable(path) => {
Expand All @@ -424,6 +448,7 @@
args,
expected_type,
location,
variables_defined,
),
Err(error) => {
self.push_err(error, location.file);
Expand All @@ -433,16 +458,21 @@
}
}
ExpressionKind::Parenthesized(expr) => {
self.expression_to_constructor(*expr, args, expected_type)
self.expression_to_constructor(*expr, args, expected_type, variables_defined)
}
ExpressionKind::Interned(id) => {
let kind = self.interner.get_expression_kind(id);
let expr = Expression::new(kind.clone(), name.location);
self.expression_to_constructor(expr, args, expected_type)
self.expression_to_constructor(expr, args, expected_type, variables_defined)
}
ExpressionKind::InternedStatement(id) => {
if let StatementKind::Expression(expr) = self.interner.get_statement_kind(id) {
self.expression_to_constructor(expr.clone(), args, expected_type)
self.expression_to_constructor(
expr.clone(),
args,
expected_type,
variables_defined,
)
} else {
panic!("Invalid expr kind {name}")
}
Expand All @@ -457,6 +487,7 @@
args: Vec<Expression>,
expected_type: &Type,
location: Location,
variables_defined: &mut Vec<Ident>,
) -> Pattern {
let span = location.span;

Expand Down Expand Up @@ -515,7 +546,7 @@

let args = args.into_iter().zip(expected_arg_types);
let args = vecmap(args, |(arg, expected_arg_type)| {
self.expression_to_pattern(arg, &expected_arg_type)
self.expression_to_pattern(arg, &expected_arg_type, variables_defined)
});
let constructor = Constructor::Variant(actual_type, variant_index);
Pattern::Constructor(constructor, args)
Expand Down Expand Up @@ -656,7 +687,7 @@

/// Compiles the cases and fallback cases for integer and range patterns.
///
/// Integers have an infinite number of constructors, so we specialise the

Check warning on line 690 in compiler/noirc_frontend/src/elaborator/enums.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (specialise)
/// compilation of integer and range patterns.
fn compile_int_cases(
&mut self,
Expand Down
10 changes: 9 additions & 1 deletion compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ pub enum ResolverError {
LoopNotYetSupported { span: Span },
#[error("Expected a trait but found {found}")]
ExpectedTrait { found: String, span: Span },
#[error("Variable '{existing}' was already defined in the same match pattern")]
VariableAlreadyDefinedInPattern { existing: Ident, new_span: Span },
}

impl ResolverError {
Expand Down Expand Up @@ -691,8 +693,14 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
format!("Expected a trait, found {found}"),
String::new(),
*span)

}
ResolverError::VariableAlreadyDefinedInPattern { existing, new_span } => {
let message = format!("Variable `{existing}` was already defined in the same match pattern");
let secondary = format!("`{existing}` redefined here");
let mut error = Diagnostic::simple_error(message, secondary, *new_span);
error.add_secondary(format!("`{existing}` was previously defined here"), existing.span());
error
},
}
}
}
19 changes: 19 additions & 0 deletions compiler/noirc_frontend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3197,7 +3197,7 @@
}

#[test]
fn dont_infer_globals_to_u32_from_type_use() {

Check warning on line 3200 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (dont)
let src = r#"
global ARRAY_LEN = 3;
global STR_LEN: _ = 2;
Expand Down Expand Up @@ -3227,7 +3227,7 @@
}

#[test]
fn dont_infer_partial_global_types() {

Check warning on line 3230 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (dont)
let src = r#"
pub global ARRAY: [Field; _] = [0; 3];
pub global NESTED_ARRAY: [[Field; _]; 3] = [[]; 3];
Expand Down Expand Up @@ -3470,7 +3470,7 @@
// wouldn't panic due to infinite recursion, but the errors asserted here
// come from the compilation checks, which does static analysis to catch the
// problem before it even has a chance to cause a panic.
let srcs = vec![

Check warning on line 3473 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (srcs)
r#"
fn main() {
main()
Expand Down Expand Up @@ -3532,7 +3532,7 @@
"#,
];

for src in srcs {

Check warning on line 3535 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (srcs)
let errors = get_program_errors(src);
assert!(
!errors.is_empty(),
Expand All @@ -3551,7 +3551,7 @@

#[test]
fn unconditional_recursion_pass() {
let srcs = vec![

Check warning on line 3554 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (srcs)
r#"
fn main() {
if false { main(); }
Expand Down Expand Up @@ -3593,7 +3593,7 @@
"#,
];

for src in srcs {

Check warning on line 3596 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (srcs)
assert_no_errors(src);
}
}
Expand Down Expand Up @@ -4475,3 +4475,22 @@

assert!(matches!(error.reason(), Some(ParserErrorReason::ExperimentalFeature(_))));
}

#[test]
fn errors_on_repeated_match_variables_in_pattern() {
let src = r#"
fn main() {
match (1, 2) {
(_x, _x) => (),
}
}
"#;

let errors = get_program_errors(src);
assert_eq!(errors.len(), 1);

assert!(matches!(
&errors[0].0,
CompilationError::ResolverError(ResolverError::VariableAlreadyDefinedInPattern { .. })
));
}
Loading