diff --git a/.github/ci-cd-scripts/upload-results.sh b/.github/ci-cd-scripts/upload-results.sh index e4d59b288..e7580201a 100755 --- a/.github/ci-cd-scripts/upload-results.sh +++ b/.github/ci-cd-scripts/upload-results.sh @@ -1,13 +1,41 @@ #!/bin/bash set -euo pipefail -BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}" -COMMIT="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}" +if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then + exit 0 +fi -curl --request POST \ +project="https://github.com/KittyCAD/modeling-app" +branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}" +commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}" + +echo "Uploading batch results" +curl --silent --request POST \ --header "X-API-Key: ${TAB_API_KEY}" \ - --form "project=https://github.com/KittyCAD/modeling-app" \ - --form "branch=${BRANCH}" \ - --form "commit=${COMMIT}" \ + --form "project=${project}" \ + --form "branch=${branch}" \ + --form "commit=${commit}" \ --form "tests=@test-results/junit.xml" \ + --form "CI_COMMIT_SHA=${CI_COMMIT_SHA:-}" \ + --form "CI_PR_NUMBER=${CI_PR_NUMBER:-}" \ + --form "GITHUB_BASE_REF=${GITHUB_BASE_REF:-}" \ + --form "GITHUB_EVENT_NAME=${GITHUB_EVENT_NAME:-}" \ + --form "GITHUB_HEAD_REF=${GITHUB_HEAD_REF:-}" \ + --form "GITHUB_REF_NAME=${GITHUB_REF_NAME:-}" \ + --form "GITHUB_REF=${GITHUB_REF:-}" \ + --form "GITHUB_SHA=${GITHUB_SHA:-}" \ + --form "GITHUB_WORKFLOW=${GITHUB_WORKFLOW:-}" \ + --form "RUNNER_ARCH=${RUNNER_ARCH:-}" \ ${TAB_API_URL}/api/results/bulk + +echo +echo "Sharing updated report" +curl --silent --request POST \ + --header "Content-Type: application/json" \ + --header "X-API-Key: ${TAB_API_KEY}" \ + --data "{ + \"project\": \"${project}\", + \"branch\": \"${branch}\", + \"commit\": \"${commit}\" + }" \ + ${TAB_API_URL}/api/share diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml index 74d77dee8..536a813d6 100644 --- a/.github/workflows/cargo-test.yml +++ b/.github/workflows/cargo-test.yml @@ -188,6 +188,8 @@ jobs: env: TAB_API_URL: ${{ secrets.TAB_API_URL }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }} + CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} + CI_PR_NUMBER: ${{ github.event.pull_request.number }} run-wasm-tests: name: Run wasm tests strategy: diff --git a/rust/kcl-lib/src/execution/exec_ast.rs b/rust/kcl-lib/src/execution/exec_ast.rs index b36cd1dcd..b7272cc92 100644 --- a/rust/kcl-lib/src/execution/exec_ast.rs +++ b/rust/kcl-lib/src/execution/exec_ast.rs @@ -18,10 +18,10 @@ use crate::{ }, modules::{ModuleId, ModulePath, ModuleRepr}, parsing::ast::types::{ - Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, - CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility, - LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression, - PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator, + Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator, + BinaryPart, BodyItem, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, + ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, + ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator, }, source_range::SourceRange, std::{ @@ -707,17 +707,25 @@ impl ExecutorContext { // TODO this lets us use the label as a variable name, but not as a tag in most cases result } - Expr::AscribedExpression(expr) => { - let result = self - .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind) - .await?; - apply_ascription(&result, &expr.ty, exec_state, expr.into())? - } + Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?, }; Ok(item) } } +impl Node { + #[async_recursion] + pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result { + let metadata = Metadata { + source_range: SourceRange::from(self), + }; + let result = ctx + .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression) + .await?; + apply_ascription(&result, &self.ty, exec_state, self.into()) + } +} + fn apply_ascription( value: &KclValue, ty: &Node, @@ -758,6 +766,7 @@ impl BinaryPart { BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await, BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state), BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await, + BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await, } } } @@ -2638,4 +2647,10 @@ sketch001 = startSketchOn(XY) parse_execute(ast).await.unwrap(); } + + #[tokio::test(flavor = "multi_thread")] + async fn ascription_in_binop() { + let ast = r#"foo = tan(0): number(rad) - 4deg"#; + parse_execute(ast).await.unwrap(); + } } diff --git a/rust/kcl-lib/src/lsp/kcl/hover.rs b/rust/kcl-lib/src/lsp/kcl/hover.rs index 56869250e..844fdd9f8 100644 --- a/rust/kcl-lib/src/lsp/kcl/hover.rs +++ b/rust/kcl-lib/src/lsp/kcl/hover.rs @@ -150,6 +150,7 @@ impl BinaryPart { unary_expression.get_hover_value_for_position(pos, code, opts) } BinaryPart::IfExpression(e) => e.get_hover_value_for_position(pos, code, opts), + BinaryPart::AscribedExpression(e) => e.expr.get_hover_value_for_position(pos, code, opts), BinaryPart::MemberExpression(member_expression) => { member_expression.get_hover_value_for_position(pos, code, opts) } diff --git a/rust/kcl-lib/src/parsing/ast/digest.rs b/rust/kcl-lib/src/parsing/ast/digest.rs index 9f3f22642..bf8a5b5aa 100644 --- a/rust/kcl-lib/src/parsing/ast/digest.rs +++ b/rust/kcl-lib/src/parsing/ast/digest.rs @@ -162,6 +162,7 @@ impl BinaryPart { BinaryPart::UnaryExpression(ue) => ue.compute_digest(), BinaryPart::MemberExpression(me) => me.compute_digest(), BinaryPart::IfExpression(e) => e.compute_digest(), + BinaryPart::AscribedExpression(e) => e.compute_digest(), } } } diff --git a/rust/kcl-lib/src/parsing/ast/mod.rs b/rust/kcl-lib/src/parsing/ast/mod.rs index 5ae0a068c..7d9936af2 100644 --- a/rust/kcl-lib/src/parsing/ast/mod.rs +++ b/rust/kcl-lib/src/parsing/ast/mod.rs @@ -52,6 +52,7 @@ impl BinaryPart { BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id, BinaryPart::MemberExpression(member_expression) => member_expression.module_id, BinaryPart::IfExpression(e) => e.module_id, + BinaryPart::AscribedExpression(e) => e.module_id, } } } diff --git a/rust/kcl-lib/src/parsing/ast/types/mod.rs b/rust/kcl-lib/src/parsing/ast/types/mod.rs index fe0348fe1..87f6fd7f0 100644 --- a/rust/kcl-lib/src/parsing/ast/types/mod.rs +++ b/rust/kcl-lib/src/parsing/ast/types/mod.rs @@ -1215,6 +1215,7 @@ impl From<&BinaryPart> for Expr { BinaryPart::UnaryExpression(unary_expression) => Expr::UnaryExpression(unary_expression.clone()), BinaryPart::MemberExpression(member_expression) => Expr::MemberExpression(member_expression.clone()), BinaryPart::IfExpression(e) => Expr::IfExpression(e.clone()), + BinaryPart::AscribedExpression(e) => Expr::AscribedExpression(e.clone()), } } } @@ -1281,6 +1282,7 @@ pub enum BinaryPart { UnaryExpression(BoxNode), MemberExpression(BoxNode), IfExpression(BoxNode), + AscribedExpression(BoxNode), } impl From for SourceRange { @@ -1306,6 +1308,7 @@ impl BinaryPart { BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(), BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(), BinaryPart::IfExpression(e) => e.get_constraint_level(), + BinaryPart::AscribedExpression(e) => e.expr.get_constraint_level(), } } @@ -1324,6 +1327,7 @@ impl BinaryPart { } BinaryPart::MemberExpression(_) => {} BinaryPart::IfExpression(e) => e.replace_value(source_range, new_value), + BinaryPart::AscribedExpression(e) => e.expr.replace_value(source_range, new_value), } } @@ -1336,6 +1340,7 @@ impl BinaryPart { BinaryPart::UnaryExpression(unary_expression) => unary_expression.start, BinaryPart::MemberExpression(member_expression) => member_expression.start, BinaryPart::IfExpression(e) => e.start, + BinaryPart::AscribedExpression(e) => e.start, } } @@ -1348,6 +1353,7 @@ impl BinaryPart { BinaryPart::UnaryExpression(unary_expression) => unary_expression.end, BinaryPart::MemberExpression(member_expression) => member_expression.end, BinaryPart::IfExpression(e) => e.end, + BinaryPart::AscribedExpression(e) => e.end, } } @@ -1369,6 +1375,7 @@ impl BinaryPart { member_expression.rename_identifiers(old_name, new_name) } BinaryPart::IfExpression(ref mut if_expression) => if_expression.rename_identifiers(old_name, new_name), + BinaryPart::AscribedExpression(ref mut e) => e.expr.rename_identifiers(old_name, new_name), } } } diff --git a/rust/kcl-lib/src/parsing/parser.rs b/rust/kcl-lib/src/parsing/parser.rs index 668b207a0..0d48bdcdf 100644 --- a/rust/kcl-lib/src/parsing/parser.rs +++ b/rust/kcl-lib/src/parsing/parser.rs @@ -582,6 +582,26 @@ fn binary_operator(i: &mut TokenSlice) -> PResult { "<=" => BinaryOperator::Lte, "|" => BinaryOperator::Or, "&" => BinaryOperator::And, + "||" => { + ParseContext::err( + CompilationError::err( + token.as_source_range(), + "`||` is not an operator, did you mean to use `|`?", + ) + .with_suggestion("Replace `||` with `|`", "|", None, Tag::None), + ); + BinaryOperator::Or + } + "&&" => { + ParseContext::err( + CompilationError::err( + token.as_source_range(), + "`&&` is not an operator, did you mean to use `&`?", + ) + .with_suggestion("Replace `&&` with `&`", "&", None, Tag::None), + ); + BinaryOperator::And + } _ => { return Err(CompilationError::fatal( token.as_source_range(), @@ -611,8 +631,7 @@ fn operand(i: &mut TokenSlice) -> PResult { | Expr::ArrayExpression(_) | Expr::ArrayRangeExpression(_) | Expr::ObjectExpression(_) - | Expr::LabelledExpression(..) - | Expr::AscribedExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)), + | Expr::LabelledExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)), Expr::None(_) => { return Err(CompilationError::fatal( source_range, @@ -638,6 +657,7 @@ fn operand(i: &mut TokenSlice) -> PResult { Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x), Expr::MemberExpression(x) => BinaryPart::MemberExpression(x), Expr::IfExpression(x) => BinaryPart::IfExpression(x), + Expr::AscribedExpression(x) => BinaryPart::AscribedExpression(x), }; Ok(expr) }) @@ -2048,7 +2068,7 @@ fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult { } fn possible_operands(i: &mut TokenSlice) -> PResult { - alt(( + let mut expr = alt(( unary_expression.map(Box::new).map(Expr::UnaryExpression), bool_value.map(Expr::Literal), member_expression.map(Box::new).map(Expr::MemberExpression), @@ -2061,7 +2081,14 @@ fn possible_operands(i: &mut TokenSlice) -> PResult { .context(expected( "a KCL value which can be used as an argument/operand to an operator", )) - .parse_next(i) + .parse_next(i)?; + + let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?; + if let Some((_, _, ty)) = ty { + expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty))) + } + + Ok(expr) } /// Parse an item visibility specifier, e.g. export. @@ -4511,6 +4538,13 @@ export fn cos(num: number(rad)): number(_) {}"#; assert_eq!(errs.len(), 3, "found: {errs:#?}"); } + #[test] + fn error_double_and() { + let (_, errs) = assert_no_fatal("foo = true && false"); + assert_eq!(errs.len(), 1, "found: {errs:#?}"); + assert!(errs[0].message.contains("`&&`") && errs[0].message.contains("`&`") && errs[0].suggestion.is_some()); + } + #[test] fn error_type_ascription() { let (_, errs) = assert_no_fatal("a + b: number"); diff --git a/rust/kcl-lib/src/parsing/token/tokeniser.rs b/rust/kcl-lib/src/parsing/token/tokeniser.rs index 5ae138930..b4553cccc 100644 --- a/rust/kcl-lib/src/parsing/token/tokeniser.rs +++ b/rust/kcl-lib/src/parsing/token/tokeniser.rs @@ -181,7 +181,7 @@ fn word(i: &mut Input<'_>) -> PResult { fn operator(i: &mut Input<'_>) -> PResult { let (value, range) = alt(( - ">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "^", "|", "&", + ">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "^", "||", "&&", "|", "&", )) .with_span() .parse_next(i)?; diff --git a/rust/kcl-lib/src/unparser.rs b/rust/kcl-lib/src/unparser.rs index b4055ff83..8c9e6c71f 100644 --- a/rust/kcl-lib/src/unparser.rs +++ b/rust/kcl-lib/src/unparser.rs @@ -2,11 +2,11 @@ use std::fmt::Write; use crate::parsing::{ ast::types::{ - Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, - CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression, IfExpression, - ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, - MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter, PipeExpression, - Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind, + Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator, + BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression, + IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, + LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter, + PipeExpression, Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind, }, deprecation, DeprecationKind, PIPE_OPERATOR, }; @@ -308,18 +308,7 @@ impl Expr { result += &e.label.name; result } - Expr::AscribedExpression(e) => { - let mut result = e.expr.recast(options, indentation_level, ctxt); - if matches!( - e.expr, - Expr::BinaryExpression(..) | Expr::PipeExpression(..) | Expr::UnaryExpression(..) - ) { - result = format!("({result})"); - } - result += ": "; - result += &e.ty.to_string(); - result - } + Expr::AscribedExpression(e) => e.recast(options, indentation_level, ctxt), Expr::None(_) => { unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115") } @@ -327,6 +316,21 @@ impl Expr { } } +impl AscribedExpression { + fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String { + let mut result = self.expr.recast(options, indentation_level, ctxt); + if matches!( + self.expr, + Expr::BinaryExpression(..) | Expr::PipeExpression(..) | Expr::UnaryExpression(..) + ) { + result = format!("({result})"); + } + result += ": "; + result += &self.ty.to_string(); + result + } +} + impl BinaryPart { fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { match &self { @@ -345,6 +349,7 @@ impl BinaryPart { BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options), BinaryPart::MemberExpression(member_expression) => member_expression.recast(), BinaryPart::IfExpression(e) => e.recast(options, indentation_level, ExprContext::Other), + BinaryPart::AscribedExpression(e) => e.recast(options, indentation_level, ExprContext::Other), } } } @@ -722,6 +727,7 @@ impl UnaryExpression { | BinaryPart::Name(_) | BinaryPart::MemberExpression(_) | BinaryPart::IfExpression(_) + | BinaryPart::AscribedExpression(_) | BinaryPart::CallExpressionKw(_) => { format!("{}{}", &self.operator, self.argument.recast(options, 0)) } diff --git a/rust/kcl-lib/src/walk/ast_node.rs b/rust/kcl-lib/src/walk/ast_node.rs index 7de47e114..2a99f2493 100644 --- a/rust/kcl-lib/src/walk/ast_node.rs +++ b/rust/kcl-lib/src/walk/ast_node.rs @@ -221,6 +221,7 @@ impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> { types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(), types::BinaryPart::MemberExpression(me) => me.as_ref().into(), types::BinaryPart::IfExpression(e) => e.as_ref().into(), + types::BinaryPart::AscribedExpression(e) => e.as_ref().into(), } } }