Skip to content

Commit d80a9d7

Browse files
authored
fix: allow multiple _ parameters, and disallow _ as an expression you can read from (#6657)
1 parent 50f4aa7 commit d80a9d7

5 files changed

Lines changed: 110 additions & 36 deletions

File tree

compiler/noirc_frontend/src/debug/mod.rs

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,16 @@ impl DebugInstrumenter {
6767
self.insert_state_set_oracle(module, 8);
6868
}
6969

70-
fn insert_var(&mut self, var_name: &str) -> SourceVarId {
70+
fn insert_var(&mut self, var_name: &str) -> Option<SourceVarId> {
71+
if var_name == "_" {
72+
return None;
73+
}
74+
7175
let var_id = SourceVarId(self.next_var_id);
7276
self.next_var_id += 1;
7377
self.variables.insert(var_id, var_name.to_string());
7478
self.scope.last_mut().unwrap().insert(var_name.to_string(), var_id);
75-
var_id
79+
Some(var_id)
7680
}
7781

7882
fn lookup_var(&self, var_name: &str) -> Option<SourceVarId> {
@@ -107,9 +111,9 @@ impl DebugInstrumenter {
107111
.flat_map(|param| {
108112
pattern_vars(&param.pattern)
109113
.iter()
110-
.map(|(id, _is_mut)| {
111-
let var_id = self.insert_var(&id.0.contents);
112-
build_assign_var_stmt(var_id, id_expr(id))
114+
.filter_map(|(id, _is_mut)| {
115+
let var_id = self.insert_var(&id.0.contents)?;
116+
Some(build_assign_var_stmt(var_id, id_expr(id)))
113117
})
114118
.collect::<Vec<_>>()
115119
})
@@ -225,13 +229,28 @@ impl DebugInstrumenter {
225229
}
226230
})
227231
.collect();
228-
let vars_exprs: Vec<ast::Expression> = vars.iter().map(|(id, _)| id_expr(id)).collect();
232+
let vars_exprs: Vec<ast::Expression> = vars
233+
.iter()
234+
.map(|(id, _)| {
235+
// We don't want to generate an expression to read from "_".
236+
// And since this expression is going to be assigned to "_" so it doesn't matter
237+
// what it is, we can use `()` for it.
238+
if id.0.contents == "_" {
239+
ast::Expression {
240+
kind: ast::ExpressionKind::Literal(ast::Literal::Unit),
241+
span: id.span(),
242+
}
243+
} else {
244+
id_expr(id)
245+
}
246+
})
247+
.collect();
229248

230249
let mut block_stmts =
231250
vec![ast::Statement { kind: ast::StatementKind::Let(let_stmt.clone()), span: *span }];
232-
block_stmts.extend(vars.iter().map(|(id, _)| {
233-
let var_id = self.insert_var(&id.0.contents);
234-
build_assign_var_stmt(var_id, id_expr(id))
251+
block_stmts.extend(vars.iter().filter_map(|(id, _)| {
252+
let var_id = self.insert_var(&id.0.contents)?;
253+
Some(build_assign_var_stmt(var_id, id_expr(id)))
235254
}));
236255
block_stmts.push(ast::Statement {
237256
kind: ast::StatementKind::Expression(ast::Expression {
@@ -422,21 +441,31 @@ impl DebugInstrumenter {
422441
let var_name = &for_stmt.identifier.0.contents;
423442
let var_id = self.insert_var(var_name);
424443

425-
let set_stmt = build_assign_var_stmt(var_id, id_expr(&for_stmt.identifier));
426-
let drop_stmt = build_drop_var_stmt(var_id, Span::empty(for_stmt.span.end()));
444+
let set_and_drop_stmt = var_id.map(|var_id| {
445+
(
446+
build_assign_var_stmt(var_id, id_expr(&for_stmt.identifier)),
447+
build_drop_var_stmt(var_id, Span::empty(for_stmt.span.end())),
448+
)
449+
});
427450

428451
self.walk_expr(&mut for_stmt.block);
452+
453+
let mut statements = Vec::new();
454+
let block_statement = ast::Statement {
455+
kind: ast::StatementKind::Semi(for_stmt.block.clone()),
456+
span: for_stmt.block.span,
457+
};
458+
459+
if let Some((set_stmt, drop_stmt)) = set_and_drop_stmt {
460+
statements.push(set_stmt);
461+
statements.push(block_statement);
462+
statements.push(drop_stmt);
463+
} else {
464+
statements.push(block_statement);
465+
}
466+
429467
for_stmt.block = ast::Expression {
430-
kind: ast::ExpressionKind::Block(ast::BlockExpression {
431-
statements: vec![
432-
set_stmt,
433-
ast::Statement {
434-
kind: ast::StatementKind::Semi(for_stmt.block.clone()),
435-
span: for_stmt.block.span,
436-
},
437-
drop_stmt,
438-
],
439-
}),
468+
kind: ast::ExpressionKind::Block(ast::BlockExpression { statements }),
440469
span: for_stmt.span,
441470
};
442471
}

compiler/noirc_frontend/src/elaborator/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@ impl<'context> Elaborator<'context> {
440440
// so we need to reintroduce the same IDs into scope here.
441441
for parameter in &func_meta.parameter_idents {
442442
let name = self.interner.definition_name(parameter.id).to_owned();
443+
if name == "_" {
444+
continue;
445+
}
443446
let warn_if_unused = !(func_meta.trait_impl.is_some() && name == "self");
444447
self.add_existing_variable_to_scope(name, parameter.clone(), warn_if_unused);
445448
}

compiler/noirc_frontend/src/elaborator/patterns.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -331,16 +331,18 @@ impl<'context> Elaborator<'context> {
331331
let resolver_meta =
332332
ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused };
333333

334-
let scope = self.scopes.get_mut_scope();
335-
let old_value = scope.add_key_value(name.clone(), resolver_meta);
336-
337-
if !allow_shadowing {
338-
if let Some(old_value) = old_value {
339-
self.push_err(ResolverError::DuplicateDefinition {
340-
name,
341-
first_span: old_value.ident.location.span,
342-
second_span: location.span,
343-
});
334+
if name != "_" {
335+
let scope = self.scopes.get_mut_scope();
336+
let old_value = scope.add_key_value(name.clone(), resolver_meta);
337+
338+
if !allow_shadowing {
339+
if let Some(old_value) = old_value {
340+
self.push_err(ResolverError::DuplicateDefinition {
341+
name,
342+
first_span: old_value.ident.location.span,
343+
second_span: location.span,
344+
});
345+
}
344346
}
345347
}
346348

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,21 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
223223
*span,
224224
)
225225
}
226-
ResolverError::VariableNotDeclared { name, span } => Diagnostic::simple_error(
227-
format!("cannot find `{name}` in this scope "),
228-
"not found in this scope".to_string(),
229-
*span,
230-
),
226+
ResolverError::VariableNotDeclared { name, span } => {
227+
if name == "_" {
228+
Diagnostic::simple_error(
229+
"in expressions, `_` can only be used on the left-hand side of an assignment".to_string(),
230+
"`_` not allowed here".to_string(),
231+
*span,
232+
)
233+
} else {
234+
Diagnostic::simple_error(
235+
format!("cannot find `{name}` in this scope"),
236+
"not found in this scope".to_string(),
237+
*span,
238+
)
239+
}
240+
},
231241
ResolverError::PathIsNotIdent { span } => Diagnostic::simple_error(
232242
"cannot use path as an identifier".to_string(),
233243
String::new(),

compiler/noirc_frontend/src/tests.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3845,3 +3845,33 @@ fn disallows_export_attribute_on_trait_impl_method() {
38453845
));
38463846
});
38473847
}
3848+
3849+
#[test]
3850+
fn allows_multiple_underscore_parameters() {
3851+
let src = r#"
3852+
pub fn foo(_: i32, _: i64) {}
3853+
3854+
fn main() {}
3855+
"#;
3856+
assert_no_errors(src);
3857+
}
3858+
3859+
#[test]
3860+
fn disallows_underscore_on_right_hand_side() {
3861+
let src = r#"
3862+
fn main() {
3863+
let _ = 1;
3864+
let _x = _;
3865+
}
3866+
"#;
3867+
let errors = get_program_errors(src);
3868+
assert_eq!(errors.len(), 1);
3869+
3870+
let CompilationError::ResolverError(ResolverError::VariableNotDeclared { name, .. }) =
3871+
&errors[0].0
3872+
else {
3873+
panic!("Expected a VariableNotDeclared error, got {:?}", errors[0].0);
3874+
};
3875+
3876+
assert_eq!(name, "_");
3877+
}

0 commit comments

Comments
 (0)