@ -33,6 +33,21 @@ pub(crate) const IMPL_PRIMITIVE: &str = "primitive";
|
||||
pub(super) const IMPL_VALUES: [&str; 3] = [IMPL_RUST, IMPL_KCL, IMPL_PRIMITIVE];
|
||||
|
||||
pub(crate) const DEPRECATED: &str = "deprecated";
|
||||
pub(crate) const WARNINGS: &str = "warnings";
|
||||
pub(crate) const WARN_ALLOW: &str = "allow";
|
||||
pub(crate) const WARN_DENY: &str = "deny";
|
||||
pub(crate) const WARN_UNKNOWN_UNITS: &str = "unknownUnits";
|
||||
pub(crate) const WARN_UNKNOWN_ATTR: &str = "unknownAttribute";
|
||||
pub(crate) const WARN_MOD_RETURN_VALUE: &str = "moduleReturnValue";
|
||||
pub(crate) const WARN_DEPRECATED: &str = "deprecated";
|
||||
pub(crate) const WARN_IGNORED_Z_AXIS: &str = "ignoredZAxis";
|
||||
pub(super) const WARN_VALUES: [&str; 5] = [
|
||||
WARN_UNKNOWN_UNITS,
|
||||
WARN_UNKNOWN_ATTR,
|
||||
WARN_MOD_RETURN_VALUE,
|
||||
WARN_DEPRECATED,
|
||||
WARN_IGNORED_Z_AXIS,
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
|
||||
pub enum Impl {
|
||||
@ -92,6 +107,64 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
|
||||
)))
|
||||
}
|
||||
|
||||
pub(super) fn many_of(
|
||||
expr: &Expr,
|
||||
of: &[&'static str],
|
||||
source_range: SourceRange,
|
||||
) -> Result<Vec<&'static str>, KclError> {
|
||||
const UNEXPECTED_MSG: &str = "Unexpected warnings value, expected a name or array of names, e.g., `unknownUnits` or `[unknownUnits, deprecated]`";
|
||||
|
||||
let values = match expr {
|
||||
Expr::Name(name) => {
|
||||
if let Some(name) = name.local_ident() {
|
||||
vec![*name]
|
||||
} else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
UNEXPECTED_MSG.to_owned(),
|
||||
vec![expr.into()],
|
||||
)));
|
||||
}
|
||||
}
|
||||
Expr::ArrayExpression(e) => {
|
||||
let mut result = Vec::new();
|
||||
for e in &e.elements {
|
||||
if let Expr::Name(name) = e {
|
||||
if let Some(name) = name.local_ident() {
|
||||
result.push(*name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
UNEXPECTED_MSG.to_owned(),
|
||||
vec![e.into()],
|
||||
)));
|
||||
}
|
||||
result
|
||||
}
|
||||
_ => {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
UNEXPECTED_MSG.to_owned(),
|
||||
vec![expr.into()],
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
values
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
of.iter()
|
||||
.find(|vv| **vv == v)
|
||||
.ok_or_else(|| {
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Unexpected warning value: `{v}`; accepted values: {}", of.join(", "),),
|
||||
vec![source_range],
|
||||
))
|
||||
})
|
||||
.map(|v| *v)
|
||||
})
|
||||
.collect::<Result<Vec<&str>, KclError>>()
|
||||
}
|
||||
|
||||
// Returns the unparsed number literal.
|
||||
pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
|
||||
if let Expr::Literal(lit) = expr {
|
||||
|
@ -67,12 +67,52 @@ impl ExecutorContext {
|
||||
"The standard library can only be skipped at the top level scope of a file",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
exec_state.warn(CompilationError::err(
|
||||
} else if annotation.name() == Some(annotations::WARNINGS) {
|
||||
// TODO we should support setting warnings for the whole project, not just one file
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
|
||||
for p in props {
|
||||
match &*p.inner.key.name {
|
||||
annotations::WARN_ALLOW => {
|
||||
let allowed = annotations::many_of(
|
||||
&p.inner.value,
|
||||
&annotations::WARN_VALUES,
|
||||
annotation.as_source_range(),
|
||||
"Unknown annotation",
|
||||
)?;
|
||||
exec_state.mod_local.allowed_warnings = allowed;
|
||||
}
|
||||
annotations::WARN_DENY => {
|
||||
let denied = annotations::many_of(
|
||||
&p.inner.value,
|
||||
&annotations::WARN_VALUES,
|
||||
annotation.as_source_range(),
|
||||
)?;
|
||||
exec_state.mod_local.denied_warnings = denied;
|
||||
}
|
||||
name => {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
|
||||
annotations::WARN_ALLOW,
|
||||
annotations::WARN_DENY,
|
||||
),
|
||||
vec![annotation.as_source_range()],
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exec_state.err(CompilationError::err(
|
||||
annotation.as_source_range(),
|
||||
"Warnings can only be customized at the top level scope of a file",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
exec_state.warn(
|
||||
CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
|
||||
annotations::WARN_UNKNOWN_ATTR,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(no_prelude)
|
||||
}
|
||||
@ -685,7 +725,8 @@ impl ExecutorContext {
|
||||
exec_state.warn(CompilationError::err(
|
||||
metadata.source_range,
|
||||
"Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
|
||||
));
|
||||
),
|
||||
annotations::WARN_MOD_RETURN_VALUE);
|
||||
|
||||
let mut new_meta = vec![metadata.to_owned()];
|
||||
new_meta.extend(meta);
|
||||
@ -1237,7 +1278,7 @@ impl Node<BinaryExpression> {
|
||||
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),
|
||||
);
|
||||
err.tag = crate::errors::Tag::UnknownNumericUnits;
|
||||
exec_state.warn(err);
|
||||
exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1728,11 +1769,11 @@ impl Node<PipeExpression> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
errors::Severity,
|
||||
exec::UnitType,
|
||||
execution::{parse_execute, ContextType},
|
||||
ExecutorSettings, UnitLen,
|
||||
@ -2141,4 +2182,29 @@ c = ((PI * 2) / 3): number(deg)
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(result.exec_state.errors().len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn custom_warning() {
|
||||
let warn = r#"
|
||||
a = PI * 2
|
||||
"#;
|
||||
let result = parse_execute(warn).await.unwrap();
|
||||
assert_eq!(result.exec_state.errors().len(), 1);
|
||||
assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
|
||||
|
||||
let allow = r#"
|
||||
@warnings(allow = unknownUnits)
|
||||
a = PI * 2
|
||||
"#;
|
||||
let result = parse_execute(allow).await.unwrap();
|
||||
assert_eq!(result.exec_state.errors().len(), 0);
|
||||
|
||||
let deny = r#"
|
||||
@warnings(deny = [unknownUnits])
|
||||
a = PI * 2
|
||||
"#;
|
||||
let result = parse_execute(deny).await.unwrap();
|
||||
assert_eq!(result.exec_state.errors().len(), 1);
|
||||
assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use indexmap::IndexMap;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::{Group, OpArg, OpKclValue, Operation},
|
||||
kcl_value::FunctionSource,
|
||||
memory,
|
||||
@ -290,7 +291,8 @@ impl FunctionDefinition<'_> {
|
||||
callsite: SourceRange,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
if self.deprecated {
|
||||
exec_state.warn(CompilationError::err(
|
||||
exec_state.warn(
|
||||
CompilationError::err(
|
||||
callsite,
|
||||
format!(
|
||||
"{} is deprecated, see the docs for a recommended replacement",
|
||||
@ -299,7 +301,9 @@ impl FunctionDefinition<'_> {
|
||||
None => "This function".to_owned(),
|
||||
}
|
||||
),
|
||||
));
|
||||
),
|
||||
annotations::WARN_DEPRECATED,
|
||||
);
|
||||
}
|
||||
|
||||
type_check_params_kw(fn_name.as_deref(), self, &mut args.kw_args, exec_state)?;
|
||||
|
@ -7,7 +7,7 @@ use serde::Serialize;
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::{
|
||||
annotations::{SETTINGS, SETTINGS_UNIT_LENGTH},
|
||||
annotations::{self, SETTINGS, SETTINGS_UNIT_LENGTH},
|
||||
types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
|
||||
EnvironmentRef, ExecState, Face, Geometry, GeometryWithImportedGeometry, Helix, ImportedGeometry, MetaSettings,
|
||||
Metadata, Plane, Sketch, Solid, TagIdentifier,
|
||||
@ -377,6 +377,7 @@ impl KclValue {
|
||||
Some(SourceRange::new(0, 0, literal.module_id)),
|
||||
crate::errors::Tag::Deprecated,
|
||||
),
|
||||
annotations::WARN_DEPRECATED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,9 @@ pub(super) struct ModuleState {
|
||||
pub(super) path: ModulePath,
|
||||
/// Artifacts for only this module.
|
||||
pub artifacts: ModuleArtifactState,
|
||||
|
||||
pub(super) allowed_warnings: Vec<&'static str>,
|
||||
pub(super) denied_warnings: Vec<&'static str>,
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
@ -133,8 +136,19 @@ impl ExecState {
|
||||
}
|
||||
|
||||
/// Log a warning.
|
||||
pub fn warn(&mut self, mut e: CompilationError) {
|
||||
pub fn warn(&mut self, mut e: CompilationError, name: &'static str) {
|
||||
debug_assert!(annotations::WARN_VALUES.contains(&name));
|
||||
|
||||
if self.mod_local.allowed_warnings.contains(&name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.mod_local.denied_warnings.contains(&name) {
|
||||
e.severity = Severity::Error;
|
||||
} else {
|
||||
e.severity = Severity::Warning;
|
||||
}
|
||||
|
||||
self.global.errors.push(e);
|
||||
}
|
||||
|
||||
@ -502,6 +516,8 @@ impl ModuleState {
|
||||
kcl_version: "0.1".to_owned(),
|
||||
},
|
||||
artifacts: Default::default(),
|
||||
allowed_warnings: Vec::new(),
|
||||
denied_warnings: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
execution::{
|
||||
annotations,
|
||||
kcl_value::{KclValue, TypeDef},
|
||||
memory::{self},
|
||||
ExecState, Plane, PlaneInfo, Point3d,
|
||||
@ -1147,7 +1148,8 @@ impl KclValue {
|
||||
KclValue::Solid { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Plane => match self {
|
||||
PrimitiveType::Plane => {
|
||||
match self {
|
||||
KclValue::String { value: s, .. }
|
||||
if [
|
||||
"xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
|
||||
@ -1175,7 +1177,7 @@ impl KclValue {
|
||||
exec_state.warn(CompilationError::err(
|
||||
self.into(),
|
||||
"Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
|
||||
));
|
||||
), annotations::WARN_IGNORED_Z_AXIS);
|
||||
}
|
||||
|
||||
let id = exec_state.mod_local.id_generator.next_uuid();
|
||||
@ -1194,7 +1196,8 @@ impl KclValue {
|
||||
Ok(KclValue::Plane { value: Box::new(plane) })
|
||||
}
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
PrimitiveType::Face => match self {
|
||||
KclValue::Face { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
|
@ -5,6 +5,7 @@ use anyhow::Result;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
annotations,
|
||||
types::{ArrayLen, NumericType, RuntimeType},
|
||||
ExecState, KclValue,
|
||||
},
|
||||
@ -114,7 +115,7 @@ pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
exec_state.warn(CompilationError::err(
|
||||
args.source_range,
|
||||
"Calling `min` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`",
|
||||
));
|
||||
), annotations::WARN_UNKNOWN_UNITS);
|
||||
}
|
||||
|
||||
let mut result = f64::MAX;
|
||||
@ -139,7 +140,7 @@ pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
exec_state.warn(CompilationError::err(
|
||||
args.source_range,
|
||||
"Calling `max` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`",
|
||||
));
|
||||
), annotations::WARN_UNKNOWN_UNITS);
|
||||
}
|
||||
|
||||
let mut result = f64::MIN;
|
||||
|
Reference in New Issue
Block a user