Merge branch 'main' into kurt-6846

This commit is contained in:
Kurt Hutten
2025-05-12 13:17:04 +10:00
committed by GitHub
11 changed files with 134 additions and 38 deletions

View File

@ -1,13 +1,41 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}" if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then
COMMIT="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}" 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}" \ --header "X-API-Key: ${TAB_API_KEY}" \
--form "project=https://github.com/KittyCAD/modeling-app" \ --form "project=${project}" \
--form "branch=${BRANCH}" \ --form "branch=${branch}" \
--form "commit=${COMMIT}" \ --form "commit=${commit}" \
--form "tests=@test-results/junit.xml" \ --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 ${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

View File

@ -188,6 +188,8 @@ jobs:
env: env:
TAB_API_URL: ${{ secrets.TAB_API_URL }} TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} 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: run-wasm-tests:
name: Run wasm tests name: Run wasm tests
strategy: strategy:

View File

@ -18,10 +18,10 @@ use crate::{
}, },
modules::{ModuleId, ModulePath, ModuleRepr}, modules::{ModuleId, ModulePath, ModuleRepr},
parsing::ast::types::{ parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility, BinaryPart, BodyItem, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression, ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef,
PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator, ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
}, },
source_range::SourceRange, source_range::SourceRange,
std::{ 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 // TODO this lets us use the label as a variable name, but not as a tag in most cases
result result
} }
Expr::AscribedExpression(expr) => { Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
let result = self
.execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
.await?;
apply_ascription(&result, &expr.ty, exec_state, expr.into())?
}
}; };
Ok(item) Ok(item)
} }
} }
impl Node<AscribedExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
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( fn apply_ascription(
value: &KclValue, value: &KclValue,
ty: &Node<Type>, ty: &Node<Type>,
@ -758,6 +766,7 @@ impl BinaryPart {
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await, BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state), BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await, 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(); 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();
}
} }

View File

@ -150,6 +150,7 @@ impl BinaryPart {
unary_expression.get_hover_value_for_position(pos, code, opts) unary_expression.get_hover_value_for_position(pos, code, opts)
} }
BinaryPart::IfExpression(e) => e.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) => { BinaryPart::MemberExpression(member_expression) => {
member_expression.get_hover_value_for_position(pos, code, opts) member_expression.get_hover_value_for_position(pos, code, opts)
} }

View File

@ -162,6 +162,7 @@ impl BinaryPart {
BinaryPart::UnaryExpression(ue) => ue.compute_digest(), BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
BinaryPart::MemberExpression(me) => me.compute_digest(), BinaryPart::MemberExpression(me) => me.compute_digest(),
BinaryPart::IfExpression(e) => e.compute_digest(), BinaryPart::IfExpression(e) => e.compute_digest(),
BinaryPart::AscribedExpression(e) => e.compute_digest(),
} }
} }
} }

View File

@ -52,6 +52,7 @@ impl BinaryPart {
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id, BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
BinaryPart::MemberExpression(member_expression) => member_expression.module_id, BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
BinaryPart::IfExpression(e) => e.module_id, BinaryPart::IfExpression(e) => e.module_id,
BinaryPart::AscribedExpression(e) => e.module_id,
} }
} }
} }

View File

@ -1215,6 +1215,7 @@ impl From<&BinaryPart> for Expr {
BinaryPart::UnaryExpression(unary_expression) => Expr::UnaryExpression(unary_expression.clone()), BinaryPart::UnaryExpression(unary_expression) => Expr::UnaryExpression(unary_expression.clone()),
BinaryPart::MemberExpression(member_expression) => Expr::MemberExpression(member_expression.clone()), BinaryPart::MemberExpression(member_expression) => Expr::MemberExpression(member_expression.clone()),
BinaryPart::IfExpression(e) => Expr::IfExpression(e.clone()), BinaryPart::IfExpression(e) => Expr::IfExpression(e.clone()),
BinaryPart::AscribedExpression(e) => Expr::AscribedExpression(e.clone()),
} }
} }
} }
@ -1281,6 +1282,7 @@ pub enum BinaryPart {
UnaryExpression(BoxNode<UnaryExpression>), UnaryExpression(BoxNode<UnaryExpression>),
MemberExpression(BoxNode<MemberExpression>), MemberExpression(BoxNode<MemberExpression>),
IfExpression(BoxNode<IfExpression>), IfExpression(BoxNode<IfExpression>),
AscribedExpression(BoxNode<AscribedExpression>),
} }
impl From<BinaryPart> for SourceRange { impl From<BinaryPart> for SourceRange {
@ -1306,6 +1308,7 @@ impl BinaryPart {
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(), BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(), BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
BinaryPart::IfExpression(e) => e.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::MemberExpression(_) => {}
BinaryPart::IfExpression(e) => e.replace_value(source_range, new_value), 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::UnaryExpression(unary_expression) => unary_expression.start,
BinaryPart::MemberExpression(member_expression) => member_expression.start, BinaryPart::MemberExpression(member_expression) => member_expression.start,
BinaryPart::IfExpression(e) => e.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::UnaryExpression(unary_expression) => unary_expression.end,
BinaryPart::MemberExpression(member_expression) => member_expression.end, BinaryPart::MemberExpression(member_expression) => member_expression.end,
BinaryPart::IfExpression(e) => e.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) member_expression.rename_identifiers(old_name, new_name)
} }
BinaryPart::IfExpression(ref mut if_expression) => if_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),
} }
} }
} }

View File

@ -582,6 +582,26 @@ fn binary_operator(i: &mut TokenSlice) -> PResult<BinaryOperator> {
"<=" => BinaryOperator::Lte, "<=" => BinaryOperator::Lte,
"|" => BinaryOperator::Or, "|" => BinaryOperator::Or,
"&" => BinaryOperator::And, "&" => 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( return Err(CompilationError::fatal(
token.as_source_range(), token.as_source_range(),
@ -611,8 +631,7 @@ fn operand(i: &mut TokenSlice) -> PResult<BinaryPart> {
| Expr::ArrayExpression(_) | Expr::ArrayExpression(_)
| Expr::ArrayRangeExpression(_) | Expr::ArrayRangeExpression(_)
| Expr::ObjectExpression(_) | Expr::ObjectExpression(_)
| Expr::LabelledExpression(..) | Expr::LabelledExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
| Expr::AscribedExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
Expr::None(_) => { Expr::None(_) => {
return Err(CompilationError::fatal( return Err(CompilationError::fatal(
source_range, source_range,
@ -638,6 +657,7 @@ fn operand(i: &mut TokenSlice) -> PResult<BinaryPart> {
Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x), Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x),
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x), Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
Expr::IfExpression(x) => BinaryPart::IfExpression(x), Expr::IfExpression(x) => BinaryPart::IfExpression(x),
Expr::AscribedExpression(x) => BinaryPart::AscribedExpression(x),
}; };
Ok(expr) Ok(expr)
}) })
@ -2048,7 +2068,7 @@ fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult<Expr> {
} }
fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> { fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
alt(( let mut expr = alt((
unary_expression.map(Box::new).map(Expr::UnaryExpression), unary_expression.map(Box::new).map(Expr::UnaryExpression),
bool_value.map(Expr::Literal), bool_value.map(Expr::Literal),
member_expression.map(Box::new).map(Expr::MemberExpression), member_expression.map(Box::new).map(Expr::MemberExpression),
@ -2061,7 +2081,14 @@ fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
.context(expected( .context(expected(
"a KCL value which can be used as an argument/operand to an operator", "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. /// 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:#?}"); 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] #[test]
fn error_type_ascription() { fn error_type_ascription() {
let (_, errs) = assert_no_fatal("a + b: number"); let (_, errs) = assert_no_fatal("a + b: number");

View File

@ -181,7 +181,7 @@ fn word(i: &mut Input<'_>) -> PResult<Token> {
fn operator(i: &mut Input<'_>) -> PResult<Token> { fn operator(i: &mut Input<'_>) -> PResult<Token> {
let (value, range) = alt(( let (value, range) = alt((
">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "^", "|", "&", ">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "^", "||", "&&", "|", "&",
)) ))
.with_span() .with_span()
.parse_next(i)?; .parse_next(i)?;

View File

@ -2,11 +2,11 @@ use std::fmt::Write;
use crate::parsing::{ use crate::parsing::{
ast::types::{ ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression, IfExpression, BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression,
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter, PipeExpression, LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind, PipeExpression, Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind,
}, },
deprecation, DeprecationKind, PIPE_OPERATOR, deprecation, DeprecationKind, PIPE_OPERATOR,
}; };
@ -308,23 +308,27 @@ impl Expr {
result += &e.label.name; result += &e.label.name;
result result
} }
Expr::AscribedExpression(e) => { Expr::AscribedExpression(e) => e.recast(options, indentation_level, ctxt),
let mut result = e.expr.recast(options, indentation_level, ctxt); Expr::None(_) => {
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
}
}
}
}
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!( if matches!(
e.expr, self.expr,
Expr::BinaryExpression(..) | Expr::PipeExpression(..) | Expr::UnaryExpression(..) Expr::BinaryExpression(..) | Expr::PipeExpression(..) | Expr::UnaryExpression(..)
) { ) {
result = format!("({result})"); result = format!("({result})");
} }
result += ": "; result += ": ";
result += &e.ty.to_string(); result += &self.ty.to_string();
result result
} }
Expr::None(_) => {
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
}
}
}
} }
impl BinaryPart { impl BinaryPart {
@ -345,6 +349,7 @@ impl BinaryPart {
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options), BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
BinaryPart::MemberExpression(member_expression) => member_expression.recast(), BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
BinaryPart::IfExpression(e) => e.recast(options, indentation_level, ExprContext::Other), 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::Name(_)
| BinaryPart::MemberExpression(_) | BinaryPart::MemberExpression(_)
| BinaryPart::IfExpression(_) | BinaryPart::IfExpression(_)
| BinaryPart::AscribedExpression(_)
| BinaryPart::CallExpressionKw(_) => { | BinaryPart::CallExpressionKw(_) => {
format!("{}{}", &self.operator, self.argument.recast(options, 0)) format!("{}{}", &self.operator, self.argument.recast(options, 0))
} }

View File

@ -221,6 +221,7 @@ impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> {
types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(), types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(),
types::BinaryPart::MemberExpression(me) => me.as_ref().into(), types::BinaryPart::MemberExpression(me) => me.as_ref().into(),
types::BinaryPart::IfExpression(e) => e.as_ref().into(), types::BinaryPart::IfExpression(e) => e.as_ref().into(),
types::BinaryPart::AscribedExpression(e) => e.as_ref().into(),
} }
} }
} }