Skip to content

Commit 8395869

Browse files
committed
Allow or deny warnings
Signed-off-by: Nick Cameron <nrc@ncameron.org>
1 parent fe581ff commit 8395869

File tree

7 files changed

+228
-64
lines changed

7 files changed

+228
-64
lines changed

rust/kcl-lib/src/execution/annotations.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ pub(crate) const IMPL_PRIMITIVE: &str = "primitive";
3333
pub(super) const IMPL_VALUES: [&str; 3] = [IMPL_RUST, IMPL_KCL, IMPL_PRIMITIVE];
3434

3535
pub(crate) const DEPRECATED: &str = "deprecated";
36+
pub(crate) const WARNINGS: &str = "warnings";
37+
pub(crate) const WARN_ALLOW: &str = "allow";
38+
pub(crate) const WARN_DENY: &str = "deny";
39+
pub(crate) const WARN_UNKNOWN_UNITS: &str = "unknownUnits";
40+
pub(crate) const WARN_UNKNOWN_ATTR: &str = "unknownAttribute";
41+
pub(crate) const WARN_MOD_RETURN_VALUE: &str = "moduleReturnValue";
42+
pub(crate) const WARN_DEPRECATED: &str = "deprecated";
43+
pub(crate) const WARN_IGNORED_Z_AXIS: &str = "ignoredZAxis";
44+
pub(super) const WARN_VALUES: [&str; 5] = [
45+
WARN_UNKNOWN_UNITS,
46+
WARN_UNKNOWN_ATTR,
47+
WARN_MOD_RETURN_VALUE,
48+
WARN_DEPRECATED,
49+
WARN_IGNORED_Z_AXIS,
50+
];
3651

3752
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
3853
pub enum Impl {
@@ -92,6 +107,64 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
92107
)))
93108
}
94109

110+
pub(super) fn many_of(
111+
expr: &Expr,
112+
of: &[&'static str],
113+
source_range: SourceRange,
114+
) -> Result<Vec<&'static str>, KclError> {
115+
const UNEXPECTED_MSG: &str = "Unexpected warnings value, expected a name or array of names, e.g., `unknownUnits` or `[unknownUnits, deprecated]`";
116+
117+
let values = match expr {
118+
Expr::Name(name) => {
119+
if let Some(name) = name.local_ident() {
120+
vec![*name]
121+
} else {
122+
return Err(KclError::new_semantic(KclErrorDetails::new(
123+
UNEXPECTED_MSG.to_owned(),
124+
vec![expr.into()],
125+
)));
126+
}
127+
}
128+
Expr::ArrayExpression(e) => {
129+
let mut result = Vec::new();
130+
for e in &e.elements {
131+
if let Expr::Name(name) = e {
132+
if let Some(name) = name.local_ident() {
133+
result.push(*name);
134+
continue;
135+
}
136+
}
137+
return Err(KclError::new_semantic(KclErrorDetails::new(
138+
UNEXPECTED_MSG.to_owned(),
139+
vec![e.into()],
140+
)));
141+
}
142+
result
143+
}
144+
_ => {
145+
return Err(KclError::new_semantic(KclErrorDetails::new(
146+
UNEXPECTED_MSG.to_owned(),
147+
vec![expr.into()],
148+
)))
149+
}
150+
};
151+
152+
values
153+
.into_iter()
154+
.map(|v| {
155+
of.iter()
156+
.find(|vv| **vv == v)
157+
.ok_or_else(|| {
158+
KclError::new_semantic(KclErrorDetails::new(
159+
format!("Unexpected warning value: `{v}`; accepted values: {}", of.join(", "),),
160+
vec![source_range],
161+
))
162+
})
163+
.map(|v| *v)
164+
})
165+
.collect::<Result<Vec<&str>, KclError>>()
166+
}
167+
95168
// Returns the unparsed number literal.
96169
pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
97170
if let Expr::Literal(lit) = expr {

rust/kcl-lib/src/execution/exec_ast.rs

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,51 @@ impl ExecutorContext {
6767
"The standard library can only be skipped at the top level scope of a file",
6868
));
6969
}
70+
} else if annotation.name() == Some(annotations::WARNINGS) {
71+
// TODO we should support setting warnings for the whole project, not just one file
72+
if matches!(body_type, BodyType::Root) {
73+
let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
74+
for p in props {
75+
match &*p.inner.key.name {
76+
annotations::WARN_ALLOW => {
77+
let allowed = annotations::many_of(
78+
&p.inner.value,
79+
&annotations::WARN_VALUES,
80+
annotation.as_source_range(),
81+
)?;
82+
exec_state.mod_local.allowed_warnings = allowed;
83+
}
84+
annotations::WARN_DENY => {
85+
let denied = annotations::many_of(
86+
&p.inner.value,
87+
&annotations::WARN_VALUES,
88+
annotation.as_source_range(),
89+
)?;
90+
exec_state.mod_local.denied_warnings = denied;
91+
}
92+
name => {
93+
return Err(KclError::new_semantic(KclErrorDetails::new(
94+
format!(
95+
"Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
96+
annotations::WARN_ALLOW,
97+
annotations::WARN_DENY,
98+
),
99+
vec![annotation.as_source_range()],
100+
)))
101+
}
102+
}
103+
}
104+
} else {
105+
exec_state.err(CompilationError::err(
106+
annotation.as_source_range(),
107+
"Warnings can only be customized at the top level scope of a file",
108+
));
109+
}
70110
} else {
71-
exec_state.warn(CompilationError::err(
72-
annotation.as_source_range(),
73-
"Unknown annotation",
74-
));
111+
exec_state.warn(
112+
CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
113+
annotations::WARN_UNKNOWN_ATTR,
114+
);
75115
}
76116
}
77117
Ok(no_prelude)
@@ -685,7 +725,8 @@ impl ExecutorContext {
685725
exec_state.warn(CompilationError::err(
686726
metadata.source_range,
687727
"Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
688-
));
728+
),
729+
annotations::WARN_MOD_RETURN_VALUE);
689730

690731
let mut new_meta = vec![metadata.to_owned()];
691732
new_meta.extend(meta);
@@ -1237,7 +1278,7 @@ impl Node<BinaryExpression> {
12371278
format!("{} numbers which have unknown or incompatible units.\nYou can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`.", verb),
12381279
);
12391280
err.tag = crate::errors::Tag::UnknownNumericUnits;
1240-
exec_state.warn(err);
1281+
exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
12411282
}
12421283
}
12431284
}
@@ -1728,11 +1769,11 @@ impl Node<PipeExpression> {
17281769
#[cfg(test)]
17291770
mod test {
17301771
use std::sync::Arc;
1731-
17321772
use tokio::io::AsyncWriteExt;
17331773

17341774
use super::*;
17351775
use crate::{
1776+
errors::Severity,
17361777
exec::UnitType,
17371778
execution::{parse_execute, ContextType},
17381779
ExecutorSettings, UnitLen,
@@ -2141,4 +2182,29 @@ c = ((PI * 2) / 3): number(deg)
21412182
let result = parse_execute(ast).await.unwrap();
21422183
assert_eq!(result.exec_state.errors().len(), 2);
21432184
}
2185+
2186+
#[tokio::test(flavor = "multi_thread")]
2187+
async fn custom_warning() {
2188+
let warn = r#"
2189+
a = PI * 2
2190+
"#;
2191+
let result = parse_execute(warn).await.unwrap();
2192+
assert_eq!(result.exec_state.errors().len(), 1);
2193+
assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
2194+
2195+
let allow = r#"
2196+
@warnings(allow = unknownUnits)
2197+
a = PI * 2
2198+
"#;
2199+
let result = parse_execute(allow).await.unwrap();
2200+
assert_eq!(result.exec_state.errors().len(), 0);
2201+
2202+
let deny = r#"
2203+
@warnings(deny = [unknownUnits])
2204+
a = PI * 2
2205+
"#;
2206+
let result = parse_execute(deny).await.unwrap();
2207+
assert_eq!(result.exec_state.errors().len(), 1);
2208+
assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
2209+
}
21442210
}

rust/kcl-lib/src/execution/fn_call.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use indexmap::IndexMap;
44
use crate::{
55
errors::{KclError, KclErrorDetails},
66
execution::{
7+
annotations,
78
cad_op::{Group, OpArg, OpKclValue, Operation},
89
kcl_value::FunctionSource,
910
memory,
@@ -290,16 +291,19 @@ impl FunctionDefinition<'_> {
290291
callsite: SourceRange,
291292
) -> Result<Option<KclValue>, KclError> {
292293
if self.deprecated {
293-
exec_state.warn(CompilationError::err(
294-
callsite,
295-
format!(
296-
"{} is deprecated, see the docs for a recommended replacement",
297-
match &fn_name {
298-
Some(n) => format!("`{n}`"),
299-
None => "This function".to_owned(),
300-
}
294+
exec_state.warn(
295+
CompilationError::err(
296+
callsite,
297+
format!(
298+
"{} is deprecated, see the docs for a recommended replacement",
299+
match &fn_name {
300+
Some(n) => format!("`{n}`"),
301+
None => "This function".to_owned(),
302+
}
303+
),
301304
),
302-
));
305+
annotations::WARN_DEPRECATED,
306+
);
303307
}
304308

305309
type_check_params_kw(fn_name.as_deref(), self, &mut args.kw_args, exec_state)?;

rust/kcl-lib/src/execution/kcl_value.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use serde::Serialize;
77
use crate::{
88
errors::KclErrorDetails,
99
execution::{
10-
annotations::{SETTINGS, SETTINGS_UNIT_LENGTH},
10+
annotations::{self, SETTINGS, SETTINGS_UNIT_LENGTH},
1111
types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
1212
EnvironmentRef, ExecState, Face, Geometry, GeometryWithImportedGeometry, Helix, ImportedGeometry, MetaSettings,
1313
Metadata, Plane, Sketch, Solid, TagIdentifier,
@@ -377,6 +377,7 @@ impl KclValue {
377377
Some(SourceRange::new(0, 0, literal.module_id)),
378378
crate::errors::Tag::Deprecated,
379379
),
380+
annotations::WARN_DEPRECATED,
380381
);
381382
}
382383
}

rust/kcl-lib/src/execution/state.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ pub(super) struct ModuleState {
108108
pub(super) path: ModulePath,
109109
/// Artifacts for only this module.
110110
pub artifacts: ModuleArtifactState,
111+
112+
pub(super) allowed_warnings: Vec<&'static str>,
113+
pub(super) denied_warnings: Vec<&'static str>,
111114
}
112115

113116
impl ExecState {
@@ -133,8 +136,19 @@ impl ExecState {
133136
}
134137

135138
/// Log a warning.
136-
pub fn warn(&mut self, mut e: CompilationError) {
137-
e.severity = Severity::Warning;
139+
pub fn warn(&mut self, mut e: CompilationError, name: &'static str) {
140+
debug_assert!(annotations::WARN_VALUES.contains(&name));
141+
142+
if self.mod_local.allowed_warnings.contains(&name) {
143+
return;
144+
}
145+
146+
if self.mod_local.denied_warnings.contains(&name) {
147+
e.severity = Severity::Error;
148+
} else {
149+
e.severity = Severity::Warning;
150+
}
151+
138152
self.global.errors.push(e);
139153
}
140154

@@ -502,6 +516,8 @@ impl ModuleState {
502516
kcl_version: "0.1".to_owned(),
503517
},
504518
artifacts: Default::default(),
519+
allowed_warnings: Vec::new(),
520+
denied_warnings: Vec::new(),
505521
}
506522
}
507523

0 commit comments

Comments
 (0)