Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions public/kcl-samples/bench/bench-parts.kcl
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ fn connectorSketch(@plane, start) {

export fn connector(@plane, length) {
connectorSketch(plane, start = [-12, 8])
|> extrude(length = length)
|> extrude(length)
connectorSketch(plane, start = [16, 8])
|> extrude(length = length)
|> extrude(length)
return 0
}

Expand All @@ -79,7 +79,7 @@ fn seatSlatSketch(@plane) {

export fn seatSlats(@plane, length) {
seatSlatSketch(plane)
|> extrude(length = length)
|> extrude(length)
return 0
}

Expand All @@ -99,7 +99,7 @@ fn backSlatsSketch(@plane) {

export fn backSlats(@plane, length) {
b = backSlatsSketch(plane)
|> extrude(length = length)
|> extrude(length)
return b
}

Expand Down
4 changes: 2 additions & 2 deletions public/kcl-samples/enclosure/main.kcl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ holeDia = 4
sketch001 = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
Expand Down Expand Up @@ -74,7 +74,7 @@ function001([
sketch003 = startSketchOn(XY)
|> startProfile(at = [width * 1.2, 0])
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA002)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|> close()
Expand Down
84 changes: 67 additions & 17 deletions rust/kcl-lib/src/execution/exec_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1311,10 +1311,15 @@ impl Node<CallExpressionKw> {
Some(l) => {
fn_args.insert(l.name.clone(), arg);
}
None => errors.push(arg),
None => {
if let Some(id) = arg_expr.arg.ident_name() {
fn_args.insert(id.to_owned(), arg);
} else {
errors.push(arg);
}
}
}
}
let fn_args = fn_args; // remove mutability

// Evaluate the unlabeled first param, if any exists.
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
Expand All @@ -1323,12 +1328,15 @@ impl Node<CallExpressionKw> {
let value = ctx
.execute_expr(arg_expr, exec_state, &metadata, &[], StatementKind::Expression)
.await?;
Some(Arg::new(value, source_range))

let label = arg_expr.ident_name().map(str::to_owned);

Some((label, Arg::new(value, source_range)))
} else {
None
};

let args = Args::new_kw(
let mut args = Args::new_kw(
KwArgs {
unlabeled,
labeled: fn_args,
Expand All @@ -1347,6 +1355,20 @@ impl Node<CallExpressionKw> {
));
}

let formals = func.args(false);

// If it's possible the input arg was meant to be labelled and we probably don't want to use
// it as the input arg, then treat it as labelled.
if let Some((Some(label), _)) = &args.kw_args.unlabeled {
if (formals.iter().all(|a| a.label_required) || exec_state.pipe_value().is_some())
&& formals.iter().any(|a| &a.name == label && a.label_required)
&& !args.kw_args.labeled.contains_key(label)
{
let (label, arg) = args.kw_args.unlabeled.take().unwrap();
args.kw_args.labeled.insert(label.unwrap(), arg);
}
}

#[cfg(feature = "artifact-graph")]
let op = if func.feature_tree_operation() {
let op_labeled_args = args
Expand All @@ -1368,7 +1390,6 @@ impl Node<CallExpressionKw> {
None
};

let formals = func.args(false);
for (label, arg) in &args.kw_args.labeled {
match formals.iter().find(|p| &p.name == label) {
Some(p) => {
Expand Down Expand Up @@ -1865,6 +1886,21 @@ fn type_check_params_kw(
args: &mut KwArgs,
exec_state: &mut ExecState,
) -> Result<(), KclError> {
// If it's possible the input arg was meant to be labelled and we probably don't want to use
// it as the input arg, then treat it as labelled.
if let Some((Some(label), _)) = &args.unlabeled {
if (function_expression.params.iter().all(|p| p.labeled) || exec_state.pipe_value().is_some())
&& function_expression
.params
.iter()
.any(|p| &p.identifier.name == label && p.labeled)
&& !args.labeled.contains_key(label)
{
let (label, arg) = args.unlabeled.take().unwrap();
args.labeled.insert(label.unwrap(), arg);
}
}

for (label, arg) in &mut args.labeled {
match function_expression.params.iter().find(|p| &p.identifier.name == label) {
Some(p) => {
Expand Down Expand Up @@ -1959,10 +1995,11 @@ fn type_check_params_kw(
if let Some(arg) = &mut args.unlabeled {
if let Some(p) = function_expression.params.iter().find(|p| !p.labeled) {
if let Some(ty) = &p.type_ {
arg.value = arg
arg.1.value = arg
.1
.value
.coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state,
)
Expand All @@ -1974,9 +2011,9 @@ fn type_check_params_kw(
.map(|n| format!("`{}`", n))
.unwrap_or_else(|| "this function".to_owned()),
ty.inner,
arg.value.human_friendly_type()
arg.1.value.human_friendly_type()
),
source_ranges: vec![arg.source_range],
source_ranges: vec![arg.1.source_range],
})
})?;
}
Expand Down Expand Up @@ -2139,10 +2176,11 @@ impl FunctionSource {
if let Some(arg) = &mut args.kw_args.unlabeled {
if let Some(p) = ast.params.iter().find(|p| !p.labeled) {
if let Some(ty) = &p.type_ {
arg.value = arg
arg.1.value = arg
.1
.value
.coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state,
)
Expand All @@ -2152,7 +2190,7 @@ impl FunctionSource {
"The input argument of {} requires a value with type `{}`, but found {}",
props.name,
ty.inner,
arg.value.human_friendly_type(),
arg.1.value.human_friendly_type(),
),
source_ranges: vec![callsite],
})
Expand Down Expand Up @@ -2224,7 +2262,7 @@ impl FunctionSource {
.kw_args
.unlabeled
.as_ref()
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
.map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
labeled_args: op_labeled_args,
},
source_range: callsite,
Expand Down Expand Up @@ -2665,13 +2703,12 @@ a = foo()

#[tokio::test(flavor = "multi_thread")]
async fn test_sensible_error_when_missing_equals_in_kwarg() {
for (i, call) in ["f(x=1,y)", "f(x=1,3,z)", "f(x=1,y,z=1)", "f(x=1, 3 + 4, z=1)"]
for (i, call) in ["f(x=1,3,0)", "f(x=1,3,z)", "f(x=1,0,z=1)", "f(x=1, 3 + 4, z)"]
.into_iter()
.enumerate()
{
let program = format!(
"fn foo() {{ return 0 }}
y = 42
z = 0
fn f(x, y, z) {{ return 0 }}
{call}"
Expand All @@ -2691,9 +2728,10 @@ fn f(x, y, z) {{ return 0 }}

#[tokio::test(flavor = "multi_thread")]
async fn default_param_for_unlabeled() {
// Tests that the input param for myExtrude is taken from the pipeline value.
// Tests that the input param for myExtrude is taken from the pipeline value and same-name
// keyword args.
let ast = r#"fn myExtrude(@sk, length) {
return extrude(sk, length = length)
return extrude(sk, length)
}
sketch001 = startSketchOn(XY)
|> circle(center = [0, 0], radius = 93.75)
Expand All @@ -2703,6 +2741,18 @@ sketch001 = startSketchOn(XY)
parse_execute(ast).await.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
async fn dont_use_unlabelled_as_input() {
// `length` should be used as the `length` argument to extrude, not the unlabelled input
let ast = r#"length = 10
startSketchOn(XY)
|> circle(center = [0, 0], radius = 93.75)
|> extrude(length)
"#;

parse_execute(ast).await.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
async fn ascription_in_binop() {
let ast = r#"foo = tan(0): number(rad) - 4deg"#;
Expand Down
6 changes: 3 additions & 3 deletions rust/kcl-lib/src/parsing/ast/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ impl<T> Node<T> {
self.comment_start = start;
}

pub fn map_ref<'a, U: 'a>(&'a self, f: fn(&'a T) -> U) -> Node<U> {
pub fn map_ref<'a, U: 'a>(&'a self, f: impl Fn(&'a T) -> U) -> Node<U> {
Node {
inner: f(&self.inner),
start: self.start,
Expand Down Expand Up @@ -1187,7 +1187,7 @@ impl Expr {

pub fn ident_name(&self) -> Option<&str> {
match self {
Expr::Name(ident) => Some(&ident.name.name),
Expr::Name(name) => name.local_ident().map(|n| n.inner),
_ => None,
}
}
Expand Down Expand Up @@ -2371,7 +2371,7 @@ impl Name {

pub fn local_ident(&self) -> Option<Node<&str>> {
if self.path.is_empty() && !self.abs_path {
Some(self.name.map_ref(|n| &n.name))
Some(self.name.map_ref(|n| &*n.name))
} else {
None
}
Expand Down
5 changes: 4 additions & 1 deletion rust/kcl-lib/src/std/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ impl Arg {
#[derive(Debug, Clone, Default)]
pub struct KwArgs {
/// Unlabeled keyword args. Currently only the first arg can be unlabeled.
pub unlabeled: Option<Arg>,
/// If the argument was a local variable, then the first element of the tuple is its name
/// which may be used to treat this arg as a labelled arg.
pub unlabeled: Option<(Option<String>, Arg)>,
/// Labeled args.
pub labeled: IndexMap<String, Arg>,
pub errors: Vec<Arg>,
Expand Down Expand Up @@ -342,6 +344,7 @@ impl Args {
self.kw_args
.unlabeled
.as_ref()
.map(|(_, a)| a)
.or(self.args.first())
.or(self.pipe_value.as_ref())
}
Expand Down
4 changes: 2 additions & 2 deletions rust/kcl-lib/src/std/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async fn call_map_closure(
ctxt: &ExecutorContext,
) -> Result<KclValue, KclError> {
let kw_args = KwArgs {
unlabeled: Some(Arg::new(input, source_range)),
unlabeled: Some((None, Arg::new(input, source_range))),
labeled: Default::default(),
errors: Vec::new(),
};
Expand Down Expand Up @@ -104,7 +104,7 @@ async fn call_reduce_closure(
let mut labeled = IndexMap::with_capacity(1);
labeled.insert("accum".to_string(), Arg::new(accum, source_range));
let kw_args = KwArgs {
unlabeled: Some(Arg::new(elem, source_range)),
unlabeled: Some((None, Arg::new(elem, source_range))),
labeled,
errors: Vec::new(),
};
Expand Down
2 changes: 1 addition & 1 deletion rust/kcl-lib/src/std/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ async fn make_transform<T: GeometryTrait>(
meta: vec![source_range.into()],
};
let kw_args = KwArgs {
unlabeled: Some(Arg::new(repetition_num, source_range)),
unlabeled: Some((None, Arg::new(repetition_num, source_range))),
labeled: Default::default(),
errors: Vec::new(),
};
Expand Down
16 changes: 2 additions & 14 deletions rust/kcl-lib/tests/kcl_samples/enclosure/ast.snap
Original file line number Diff line number Diff line change
Expand Up @@ -456,13 +456,7 @@ description: Result of parsing enclosure.kcl
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "length",
"start": 0,
"type": "Identifier"
},
"label": null,
"arg": {
"abs_path": false,
"commentStart": 0,
Expand Down Expand Up @@ -3178,13 +3172,7 @@ description: Result of parsing enclosure.kcl
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "length",
"start": 0,
"type": "Identifier"
},
"label": null,
"arg": {
"abs_path": false,
"commentStart": 0,
Expand Down
Loading
Loading