Skip to content

Commit 8e68ce6

Browse files
authored
fix(experimental): Allow shadowing in match patterns (#7484)
1 parent 47aec5d commit 8e68ce6

File tree

3 files changed

+76
-18
lines changed

3 files changed

+76
-18
lines changed

compiler/noirc_frontend/src/elaborator/enums.rs

+48-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use noirc_errors::Location;
55

66
use crate::{
77
ast::{
8-
EnumVariant, Expression, ExpressionKind, FunctionKind, Literal, NoirEnumeration,
8+
EnumVariant, Expression, ExpressionKind, FunctionKind, Ident, Literal, NoirEnumeration,
99
StatementKind, UnresolvedType, Visibility,
1010
},
1111
elaborator::path_resolution::PathResolutionItem,
@@ -271,7 +271,8 @@ impl Elaborator<'_> {
271271

272272
let rows = vecmap(rules, |(pattern, branch)| {
273273
self.push_scope();
274-
let pattern = self.expression_to_pattern(pattern, &expected_pattern_type);
274+
let pattern =
275+
self.expression_to_pattern(pattern, &expected_pattern_type, &mut Vec::new());
275276
let columns = vec![Column::new(variable_to_match, pattern)];
276277

277278
let guard = None;
@@ -293,7 +294,12 @@ impl Elaborator<'_> {
293294
}
294295

295296
/// Convert an expression into a Pattern, defining any variables within.
296-
fn expression_to_pattern(&mut self, expression: Expression, expected_type: &Type) -> Pattern {
297+
fn expression_to_pattern(
298+
&mut self,
299+
expression: Expression,
300+
expected_type: &Type,
301+
variables_defined: &mut Vec<Ident>,
302+
) -> Pattern {
297303
let expr_location = expression.type_location();
298304
let unify_with_expected_type = |this: &mut Self, actual| {
299305
this.unify(actual, expected_type, expr_location.file, || {
@@ -333,13 +339,25 @@ impl Elaborator<'_> {
333339
Vec::new(),
334340
expected_type,
335341
location,
342+
variables_defined,
336343
),
337344
Err(_) if path_len == 1 => {
338345
// Define the variable
339346
let kind = DefinitionKind::Local(None);
340-
// TODO: `allow_shadowing` is false while I'm too lazy to add a check that we
341-
// don't define the same name multiple times in one pattern.
342-
let id = self.add_variable_decl(last_ident, false, false, true, kind).id;
347+
348+
if let Some(existing) =
349+
variables_defined.iter().find(|elem| *elem == &last_ident)
350+
{
351+
let error = ResolverError::VariableAlreadyDefinedInPattern {
352+
existing: existing.clone(),
353+
new_span: last_ident.span(),
354+
};
355+
self.push_err(error, self.file);
356+
} else {
357+
variables_defined.push(last_ident.clone());
358+
}
359+
360+
let id = self.add_variable_decl(last_ident, false, true, true, kind).id;
343361
self.interner.push_definition_type(id, expected_type.clone());
344362
Pattern::Binding(id)
345363
}
@@ -352,9 +370,12 @@ impl Elaborator<'_> {
352370
}
353371
}
354372
}
355-
ExpressionKind::Call(call) => {
356-
self.expression_to_constructor(*call.func, call.arguments, expected_type)
357-
}
373+
ExpressionKind::Call(call) => self.expression_to_constructor(
374+
*call.func,
375+
call.arguments,
376+
expected_type,
377+
variables_defined,
378+
),
358379
ExpressionKind::Constructor(_) => todo!("handle constructors"),
359380
ExpressionKind::Tuple(fields) => {
360381
let field_types = vecmap(0..fields.len(), |_| self.interner.next_type_variable());
@@ -363,21 +384,23 @@ impl Elaborator<'_> {
363384

364385
let fields = vecmap(fields.into_iter().enumerate(), |(i, field)| {
365386
let expected = field_types.get(i).unwrap_or(&Type::Error);
366-
self.expression_to_pattern(field, expected)
387+
self.expression_to_pattern(field, expected, variables_defined)
367388
});
368389

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

372-
ExpressionKind::Parenthesized(expr) => self.expression_to_pattern(*expr, expected_type),
393+
ExpressionKind::Parenthesized(expr) => {
394+
self.expression_to_pattern(*expr, expected_type, variables_defined)
395+
}
373396
ExpressionKind::Interned(id) => {
374397
let kind = self.interner.get_expression_kind(id);
375398
let expr = Expression::new(kind.clone(), expression.location);
376-
self.expression_to_pattern(expr, expected_type)
399+
self.expression_to_pattern(expr, expected_type, variables_defined)
377400
}
378401
ExpressionKind::InternedStatement(id) => {
379402
if let StatementKind::Expression(expr) = self.interner.get_statement_kind(id) {
380-
self.expression_to_pattern(expr.clone(), expected_type)
403+
self.expression_to_pattern(expr.clone(), expected_type, variables_defined)
381404
} else {
382405
panic!("Invalid expr kind {expression}")
383406
}
@@ -413,6 +436,7 @@ impl Elaborator<'_> {
413436
name: Expression,
414437
args: Vec<Expression>,
415438
expected_type: &Type,
439+
variables_defined: &mut Vec<Ident>,
416440
) -> Pattern {
417441
match name.kind {
418442
ExpressionKind::Variable(path) => {
@@ -424,6 +448,7 @@ impl Elaborator<'_> {
424448
args,
425449
expected_type,
426450
location,
451+
variables_defined,
427452
),
428453
Err(error) => {
429454
self.push_err(error, location.file);
@@ -433,16 +458,21 @@ impl Elaborator<'_> {
433458
}
434459
}
435460
ExpressionKind::Parenthesized(expr) => {
436-
self.expression_to_constructor(*expr, args, expected_type)
461+
self.expression_to_constructor(*expr, args, expected_type, variables_defined)
437462
}
438463
ExpressionKind::Interned(id) => {
439464
let kind = self.interner.get_expression_kind(id);
440465
let expr = Expression::new(kind.clone(), name.location);
441-
self.expression_to_constructor(expr, args, expected_type)
466+
self.expression_to_constructor(expr, args, expected_type, variables_defined)
442467
}
443468
ExpressionKind::InternedStatement(id) => {
444469
if let StatementKind::Expression(expr) = self.interner.get_statement_kind(id) {
445-
self.expression_to_constructor(expr.clone(), args, expected_type)
470+
self.expression_to_constructor(
471+
expr.clone(),
472+
args,
473+
expected_type,
474+
variables_defined,
475+
)
446476
} else {
447477
panic!("Invalid expr kind {name}")
448478
}
@@ -457,6 +487,7 @@ impl Elaborator<'_> {
457487
args: Vec<Expression>,
458488
expected_type: &Type,
459489
location: Location,
490+
variables_defined: &mut Vec<Ident>,
460491
) -> Pattern {
461492
let span = location.span;
462493

@@ -515,7 +546,7 @@ impl Elaborator<'_> {
515546

516547
let args = args.into_iter().zip(expected_arg_types);
517548
let args = vecmap(args, |(arg, expected_arg_type)| {
518-
self.expression_to_pattern(arg, &expected_arg_type)
549+
self.expression_to_pattern(arg, &expected_arg_type, variables_defined)
519550
});
520551
let constructor = Constructor::Variant(actual_type, variant_index);
521552
Pattern::Constructor(constructor, args)

compiler/noirc_frontend/src/hir/resolution/errors.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ pub enum ResolverError {
182182
LoopNotYetSupported { span: Span },
183183
#[error("Expected a trait but found {found}")]
184184
ExpectedTrait { found: String, span: Span },
185+
#[error("Variable '{existing}' was already defined in the same match pattern")]
186+
VariableAlreadyDefinedInPattern { existing: Ident, new_span: Span },
185187
}
186188

187189
impl ResolverError {
@@ -691,8 +693,14 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
691693
format!("Expected a trait, found {found}"),
692694
String::new(),
693695
*span)
694-
695696
}
697+
ResolverError::VariableAlreadyDefinedInPattern { existing, new_span } => {
698+
let message = format!("Variable `{existing}` was already defined in the same match pattern");
699+
let secondary = format!("`{existing}` redefined here");
700+
let mut error = Diagnostic::simple_error(message, secondary, *new_span);
701+
error.add_secondary(format!("`{existing}` was previously defined here"), existing.span());
702+
error
703+
},
696704
}
697705
}
698706
}

compiler/noirc_frontend/src/tests.rs

+19
Original file line numberDiff line numberDiff line change
@@ -4475,3 +4475,22 @@ fn errors_on_unspecified_unstable_match() {
44754475

44764476
assert!(matches!(error.reason(), Some(ParserErrorReason::ExperimentalFeature(_))));
44774477
}
4478+
4479+
#[test]
4480+
fn errors_on_repeated_match_variables_in_pattern() {
4481+
let src = r#"
4482+
fn main() {
4483+
match (1, 2) {
4484+
(_x, _x) => (),
4485+
}
4486+
}
4487+
"#;
4488+
4489+
let errors = get_program_errors(src);
4490+
assert_eq!(errors.len(), 1);
4491+
4492+
assert!(matches!(
4493+
&errors[0].0,
4494+
CompilationError::ResolverError(ResolverError::VariableAlreadyDefinedInPattern { .. })
4495+
));
4496+
}

0 commit comments

Comments
 (0)