Skip to content

Commit 6a9fed2

Browse files
committed
add warning category deny and allow
1 parent 7ffa3f9 commit 6a9fed2

3 files changed

Lines changed: 146 additions & 3 deletions

File tree

src/ast.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,15 +763,36 @@ impl Scope {
763763
}
764764
}
765765

766+
/// Category of a warning, used for per-class allow/deny control.
767+
///
768+
/// Unlike [`WarningName`], which carries instance-specific data for display,
769+
/// `WarnCategory` is a unit enum that identifies the *class* of warning so that
770+
/// callers can write `template.deny_warning(WarnCategory::UnusedVariable)` without
771+
/// needing a concrete identifier.
772+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
773+
pub enum WarnCategory {
774+
/// A variable was bound but never used.
775+
UnusedVariable,
776+
}
777+
766778
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
767779
pub enum WarningName {
768780
VariableUnused(Identifier),
769781
}
770782

783+
impl WarningName {
784+
/// Return the category this warning belongs to.
785+
pub fn category(&self) -> WarnCategory {
786+
match self {
787+
WarningName::VariableUnused(_) => WarnCategory::UnusedVariable,
788+
}
789+
}
790+
}
791+
771792
impl fmt::Display for WarningName {
772793
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
773794
match self {
774-
WarningName::VariableUnused(identifier) => write!(f, "Variable `{identifier}` is bound but never used. Prefix the variable name with `_` to silence this warning."),
795+
WarningName::VariableUnused(identifier) => write!(f, "unused variable: `{identifier}`. Prefix the variable name with `_` to silence this warning."),
775796
}
776797
}
777798
}

src/lib.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub extern crate either;
2929
pub extern crate simplicity;
3030
pub use simplicity::elements;
3131

32+
pub use crate::ast::WarnCategory;
3233
use crate::ast::Warning;
3334
use crate::debug::DebugSymbols;
3435
use crate::error::{ErrorCollector, WithFile, WithFilePath};
@@ -89,7 +90,7 @@ impl TemplateProgram {
8990
}
9091
}
9192

92-
/// Treat any warnings as hard errors.
93+
/// Treat all warnings as hard errors.
9394
///
9495
/// Returns `self` unchanged if there are no warnings.
9596
///
@@ -106,6 +107,39 @@ impl TemplateProgram {
106107
Err(ErrorCollector::to_string(&error_handler))
107108
}
108109

110+
/// Treat warnings of the given category as hard errors.
111+
///
112+
/// Returns `self` unchanged if no warnings of that category exist.
113+
///
114+
/// ## Errors
115+
///
116+
/// The program produced one or more warnings in the given category.
117+
pub fn deny_warning(self, category: WarnCategory) -> Result<Self, String> {
118+
let matching: Vec<_> = self
119+
.warnings
120+
.iter()
121+
.filter(|w| w.canonical_name.category() == category)
122+
.cloned()
123+
.collect();
124+
if matching.is_empty() {
125+
return Ok(self);
126+
}
127+
let mut error_handler =
128+
ErrorCollector::new_with_path(Arc::clone(&self.file), Arc::clone(&self.file_path));
129+
error_handler.update(matching.into_iter().map(|w| w.into()));
130+
Err(ErrorCollector::to_string(&error_handler))
131+
}
132+
133+
/// Silence warnings of the given category.
134+
///
135+
/// The matching warnings are removed from [`TemplateProgram::warnings`] and will
136+
/// not appear in [`format_warnings`](Self::format_warnings) output.
137+
pub fn allow_warning(mut self, category: WarnCategory) -> Self {
138+
self.warnings
139+
.retain(|w| w.canonical_name.category() != category);
140+
self
141+
}
142+
109143
/// Access the parameters of the program.
110144
pub fn parameters(&self) -> &Parameters {
111145
self.simfony.parameters()
@@ -968,6 +1002,38 @@ fn main() {
9681002
}"#;
9691003
assert!(warning_names(prog).is_empty());
9701004
}
1005+
1006+
#[test]
1007+
fn deny_warning_by_category_is_error() {
1008+
use crate::WarnCategory;
1009+
let prog = r#"fn main() {
1010+
let (carry, sum): (bool, u8) = jet::add_8(2, 3);
1011+
assert!(jet::eq_8(sum, 5))
1012+
}"#;
1013+
assert!(
1014+
TemplateProgram::new(prog)
1015+
.unwrap()
1016+
.deny_warning(WarnCategory::UnusedVariable)
1017+
.is_err(),
1018+
"deny_warning(UnusedVariable) should fail when there is an unused variable",
1019+
);
1020+
}
1021+
1022+
#[test]
1023+
fn allow_warning_by_category_suppresses_it() {
1024+
use crate::WarnCategory;
1025+
let prog = r#"fn main() {
1026+
let (carry, sum): (bool, u8) = jet::add_8(2, 3);
1027+
assert!(jet::eq_8(sum, 5))
1028+
}"#;
1029+
let template = TemplateProgram::new(prog)
1030+
.unwrap()
1031+
.allow_warning(WarnCategory::UnusedVariable);
1032+
assert!(
1033+
template.warnings().is_empty(),
1034+
"allow_warning(UnusedVariable) should remove the warning",
1035+
);
1036+
}
9711037
}
9721038

9731039
#[cfg(feature = "serde")]

src/main.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use base64::display::Base64Display;
22
use base64::engine::general_purpose::STANDARD;
33
use clap::{Arg, ArgAction, Command};
44

5-
use simplicityhl::{AbiMeta, TemplateProgram};
5+
use simplicityhl::{AbiMeta, TemplateProgram, WarnCategory};
66
use std::{env, fmt};
77

88
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
@@ -89,6 +89,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
8989
.action(ArgAction::SetTrue)
9090
.help("Treat warnings as errors"),
9191
)
92+
.arg(
93+
Arg::new("deny_warning")
94+
.long("deny-warning")
95+
.value_name("CATEGORY")
96+
.action(ArgAction::Append)
97+
.help("Treat warnings of a specific category as errors (unused-variable)"),
98+
)
99+
.arg(
100+
Arg::new("allow_warning")
101+
.long("allow-warning")
102+
.value_name("CATEGORY")
103+
.action(ArgAction::Append)
104+
.help("Silence warnings of a specific category (unused-variable)"),
105+
)
92106
};
93107

94108
let matches = command.get_matches();
@@ -121,13 +135,55 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
121135

122136
let deny_warnings = matches.get_flag("deny_warnings");
123137

138+
fn parse_warn_category(s: &str) -> Result<WarnCategory, String> {
139+
match s {
140+
"unused-variable" => Ok(WarnCategory::UnusedVariable),
141+
other => Err(format!(
142+
"unknown warning category `{other}`; valid categories: unused-variable"
143+
)),
144+
}
145+
}
146+
147+
let deny_categories: Vec<WarnCategory> = matches
148+
.get_many::<String>("deny_warning")
149+
.unwrap_or_default()
150+
.map(|s| parse_warn_category(s))
151+
.collect::<Result<_, _>>()
152+
.unwrap_or_else(|e| {
153+
eprintln!("\x1b[1;31merror\x1b[0m: {e}");
154+
std::process::exit(1);
155+
});
156+
157+
let allow_categories: Vec<WarnCategory> = matches
158+
.get_many::<String>("allow_warning")
159+
.unwrap_or_default()
160+
.map(|s| parse_warn_category(s))
161+
.collect::<Result<_, _>>()
162+
.unwrap_or_else(|e| {
163+
eprintln!("\x1b[1;31merror\x1b[0m: {e}");
164+
std::process::exit(1);
165+
});
166+
124167
let template = match TemplateProgram::new_with_path(prog_text, prog_file.as_str()) {
125168
Ok(t) => t,
126169
Err(e) => {
127170
eprintln!("{}", e);
128171
std::process::exit(1);
129172
}
130173
};
174+
175+
let template = allow_categories
176+
.iter()
177+
.fold(template, |t, &cat| t.allow_warning(cat));
178+
179+
let template = deny_categories
180+
.iter()
181+
.fold(Ok(template), |t, &cat| t.and_then(|t| t.deny_warning(cat)))
182+
.unwrap_or_else(|e| {
183+
eprintln!("{e}");
184+
std::process::exit(1);
185+
});
186+
131187
let n_warnings = template.warnings().len();
132188
if n_warnings > 0 {
133189
eprint!("{}", template.format_warnings(prog_file));

0 commit comments

Comments
 (0)