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
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

View File

@ -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:

View File

@ -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<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(
value: &KclValue,
ty: &Node<Type>,
@ -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();
}
}

View File

@ -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)
}

View File

@ -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(),
}
}
}

View File

@ -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,
}
}
}

View File

@ -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<UnaryExpression>),
MemberExpression(BoxNode<MemberExpression>),
IfExpression(BoxNode<IfExpression>),
AscribedExpression(BoxNode<AscribedExpression>),
}
impl From<BinaryPart> 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),
}
}
}

View File

@ -582,6 +582,26 @@ fn binary_operator(i: &mut TokenSlice) -> PResult<BinaryOperator> {
"<=" => 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<BinaryPart> {
| 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<BinaryPart> {
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<Expr> {
}
fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
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<Expr> {
.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");

View File

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

View File

@ -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))
}

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::MemberExpression(me) => me.as_ref().into(),
types::BinaryPart::IfExpression(e) => e.as_ref().into(),
types::BinaryPart::AscribedExpression(e) => e.as_ref().into(),
}
}
}