Compare commits

...

39 Commits

Author SHA1 Message Date
dac3c0c224 Delete test that is covering a basic assumption 2025-05-13 12:46:28 -04:00
99cb6a6179 [Fix] Created the safest navigate/load/open/please sir i want some more for a .kcl file (#6867)
* fix: clear scene and bust cache if rust panics

* Update onboarding following @jgomez720

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* fix: clean up

* fix: FUCK

* fix: FUCK 2.0

* fix: oh boi

* fix: oh boi

* fix: idk man

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix: take main on this, do not need a single line from my testing code

* fix: more PR cleanup from all of the testing code

* fix: trying to clean up more, ope this has a lot of other code

* fix: PR clean up

* fix: trying to get a clean branch, I had multiple other branches in here ope

* fix: more cleanup

* fix: another one

* fix: fixed the comment to be accurate

* fix: removed confusing comment

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 15:35:29 +00:00
13dbfdfaa4 SystemIOMachine: Toast default error instead of empty string (#6898)
pierremtb/issue6820-add-error-instead-of-empty-string
2025-05-13 10:28:24 -05:00
8603f5c53a Default to the feature tree, code, and file panes open (#6901)
Only include the file pane on desktop. This should encourage users to
try point-and-click and feature tree workflows. After #6629 is
completed, we should default to the code pane being closed.
2025-05-13 15:25:27 +00:00
d5cc9e8386 Check both public and internal KCL samples (#6889) 2025-05-13 09:21:53 -04:00
533e17466b Update text-to-cad success toast to tell what file got made (#6882)
Closes #5820 and provides a bit of other polish to make this toast look
a bit better.
2025-05-13 12:26:29 +00:00
01c7b69f50 Function types (#6891)
* Change Fn to fn for function types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Support args and return types in function types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Use fancy function types in the docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 14:59:23 +12:00
47feae3bd9 Register modeling commands as disabled, enable without needing to re-open the command palette (#6886)
* Allow adding and removing commands from any command bar state

* Allow commands to be configured disabled in the combobox

* Set up modeling commands to toggle `disabled` based on network status, instead of filtering

* Fix tsc
2025-05-13 01:18:50 +00:00
e7ecd655c4 Add docs on arrays and ranges (#6890)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 13:06:07 +12:00
d9e538c6ea Fix docs for object properties (#6892)
Jon removed support for obj["a"] a while ago in #6529 aka
2e754f2a11
2025-05-13 00:56:50 +00:00
e297f8286f KCL: Fix incorrect error messages (#6883)
**Problem:**

KCL's xLine and yLine functions were telling users to supply either "end" or "endAbsolute" arguments. But "end" is not a valid argument for xLine/yLine, it's actually "length" instead.

**Cause:**

xLine/yLine were using a helper function designed for `line` KCL stdlib functions, which do use `end`. 

**Solution**

Add a new param to the helper function, indicating what the label should be for relative lines. "end" for `line` calls, `length` for x/yline
2025-05-13 00:26:43 +00:00
b3bc90bbe4 Fix lower right version background in dark mode and update snapshots (#6888)
* pierremtb/adhoc/fix-version-bg-dark-mode

* Update snapshots
2025-05-12 23:46:52 +00:00
95a02cbcd7 Check all nested KCL samples (#6880) 2025-05-12 18:43:51 -04:00
a049768f1c Move some more functions to be declared in KCL (#6856)
* Move the leg functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move array functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move clone to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add a function type

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 08:29:38 +12:00
818d9a0d77 Release link and updater toast touchups on Nightly (#6881)
pierremtb/adhoc/clean-up-nightly-on-merge-more
2025-05-12 20:20:12 +00:00
1a8f80a7dc #6793 Fix file, folder renaming issues (#6869)
* fix going into rename mode for files with parents

* lastDirectoryClicked is not used

* fix file/folder rename bugs: renaming within folders

* Turn form into div to fix issue of child renaming continues into renaming the parent folder when hitting enter

* ContextMenu stopPropagation not needed anymore, maybe because of form refactor

* ContextMenu IS needed actually, with multiple nested folders

* make lint happy

* Update src/components/ContextMenu.tsx

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* re-add <form> instead of <div> for file renaming

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-12 21:59:10 +02:00
566c9eaf10 Spell out shortcut in project search bar (#6872)
dumb little zero-thought bit of polish.
2025-05-12 19:36:05 +00:00
e6485c2da1 Clean up Nightly release version & notes (#6877)
* pierremtb/adhoc/clean-up-nightly-on-merge

* To revert: test to upload builds at test/nightly

* Remove + suffix trim that's not needed anymore

* Revert "To revert: test to upload builds at test/nightly"

This reverts commit b0549d426f.
2025-05-12 14:21:09 -04:00
0479edd36a Remove unused GitHub Actions workflow for old onboarding (#6873) 2025-05-12 17:30:51 +00:00
87c1e92134 Deploy Nightly on merge to main (#6871)
* Deploy Nightly on merge to main

* Forgot one block

* Remove .github/workflows/tag-nightly.yml

* Remove scripts/get-nightly-changelog.sh

* Update .github/workflows/build-apps.yml

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-12 12:30:01 -04:00
8f950ac1b0 Always run end-to-end tests (#6870) 2025-05-12 12:23:09 -04:00
78e4f43708 Fix kcl errors indicator not scaling with multi-digit count (#6864)
Fixes #6847
2025-05-12 11:36:09 -04:00
270436f404 Fix: clearSceneAndBustCache when rust panics because it can load a stale cache (#6809)
* fix: clear scene and bust cache if rust panics

* fix: pr clean up

* fix: huh? cargo fmt doesn't do this?
2025-05-12 10:08:54 -05:00
57e61632a9 Fix to remove extraneous from package.json (#6868) 2025-05-12 14:50:44 +00:00
884191b8bb Bump kcl (#6865) 2025-05-12 14:18:55 +00:00
21b92f5f13 fix cache multi-file (#6844)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* bump kittycad.rs i need this for cli

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* bump the version so i can fix cli

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* clippy

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-12 09:07:18 -05:00
5599a75dbd make sure sketch can be exited (#6862) 2025-05-12 11:30:31 +00:00
3a06ae6e34 fix sketch on offset plane animations (#6858)
* fix sketch on offset plane animations

* trigger CI
2025-05-12 07:28:09 -04:00
22857d77e9 Fix bug with undo startSketchOn removing existing sketch (#6834)
* Fix bug with `undo startSketchOn` removing existing sketch

Fixes #6822, I believe. in this case the `variableName` was not being
marked as in-use, so I just logged out the AST and made sure this case
was covered. @Irev-Dev this is probably worth a check from you.

* Add a regression test

It's an E2E test because I'm being lazy, but it should probably be an
XState unit test at some point.

* check what's checked

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2025-05-12 16:55:39 +10:00
1a325d0b29 Fix to use correct units for array range expressions (#6837)
* Fix to use correct units for array range expressions

* Update output
2025-05-12 01:30:33 -04:00
1240b23080 Don't truncate extrude arg docs (#6854)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-12 16:48:30 +12:00
8445080d7a Some improvements to math::sqrt (#6853)
* Treat number as any rather than default

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Don't square root negative numbers

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-12 16:47:01 +12:00
bbe89f56a7 Fix the KCL any type and array coercion incorrectly nesting (#6816)
* Add sim test for any type

* Fix doc comments to match code

* Add array ascription tests

* Commit new test output

* Fix to not panic when type is undefined

* Fix to not panic on use of the any type

* Update test and generated output

* Fix error message after rebase

* Fix subtype of any

* Fix KCL to use new keyword args

* Fix to not nest MixedArray in HomArray

* Update output

* Remove all creation of MixedArray and use HomArray instead

* Rename MixedArray to Tuple

* Fix to coerce arrays the way tuples are done

* Restructure to appease the type signature extraction

* Fix TS unit test

* Update output after switch to HomArray

* Update docs

* Fix to remove edge case when creating points

* Update docs with broken point signature

* Fix display of tuples to not collide with arrays

* Change push to an array with type mismatch to be an error

* Add sim test for push type error

* Fix acription to more general array element type

* Fix to coerce point types

* Change array push to not error when item type differs

* Fix coercion tests

* Change to only flatten as a last resort and remove flattening tuples

* Contort code to appease doc generation

* Update docs

* Fix coerce axes

* Fix flattening test to test arrays instead of tuples

* Remove special subtype case for singleton coercion
2025-05-12 03:57:31 +00:00
86e8bcfe0b fix the null app explosion (#6851)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-12 03:51:13 +00:00
21ccf129d6 Accept type ascription within binary expressions (#6849)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-12 14:07:57 +12:00
dfc4b7d0c5 Non-fatal error on using && or || (#6845)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-12 13:46:17 +12:00
17b1120a27 Include metadata environment variables with results (#6838)
* Include metadata environment variables with results

* Share updated report after uploading each shard

* Hide raw curl progress
2025-05-11 21:40:11 -04:00
2b509a515b More docs for Plane, Pi, etc. (#6850)
* Document the units of PI

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add links between lang and std references

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Change signature of conversion functions

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Split foreign imports out of modules docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* More docs for Plane

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Update docs/kcl-std/consts/std-math-PI.md

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Update rust/kcl-lib/std/math.kcl

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-12 12:59:45 +12:00
97594b9a9e Proper type-checking where a function requires a tag identifier not just a tag (#6848)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-12 12:57:50 +12:00
236 changed files with 13092 additions and 39888 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

@ -7,11 +7,10 @@ on:
- main - main
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+'
- 'nightly-v[0-9]+.[0-9]+.[0-9]+'
env: env:
IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }}
IS_NIGHTLY: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'nightly-v') }} IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -95,7 +94,9 @@ jobs:
- name: Set nightly version, product name, release notes, and icons - name: Set nightly version, product name, release notes, and icons
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
run: | run: |
export VERSION=${GITHUB_REF_NAME#nightly-v} COMMIT=$(git rev-parse --short HEAD)
DATE=$(date +'%-y.%-m.%-d')
export VERSION=$DATE-main.$COMMIT
npm run files:set-version npm run files:set-version
npm run files:flip-to-nightly npm run files:flip-to-nightly
@ -306,7 +307,8 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions: permissions:
contents: write contents: write
if: ${{ github.ref_type == 'tag' }} # Equivalent to IS_RELEASE || IS_NIGHTLY (but we can't access those env vars here)
if: ${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
env: env:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }} VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }}
@ -412,17 +414,6 @@ jobs:
- name: List artifacts - name: List artifacts
run: "ls -R out" run: "ls -R out"
- name: Set more complete nightly release notes
if: ${{ env.IS_NIGHTLY == 'true' }}
run: |
# Note: preferred going this way instead of a full clone in the checkout step,
# see https://github.com/actions/checkout/issues/1471
git fetch --prune --unshallow --tags
export TAG="nightly-${VERSION}"
export PREVIOUS_TAG=$(git tag --list --sort=-committerdate "nightly-v[0-9]*" | head -n2 | tail -n1)
export NOTES=$(./scripts/get-nightly-changelog.sh)
npm run files:set-notes
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
uses: 'google-github-actions/auth@v2.1.8' uses: 'google-github-actions/auth@v2.1.8'

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

@ -1,47 +0,0 @@
name: Check Onboarding KCL
on:
pull_request:
types: [opened, synchronize]
paths:
- 'src/lib/exampleKcl.ts'
- 'public/kcl-samples/bracket/main.kcl'
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const message = '`public/kcl-samples/bracket/main.kcl` or `src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.';
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number
});
const commentExists = comments.some(comment => comment.body === message);
if (!commentExists) {
// Post a comment on the PR
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: message,
});
}

View File

@ -18,73 +18,13 @@ permissions:
jobs: jobs:
conditions:
runs-on: ubuntu-latest
outputs:
significant: ${{ steps.path-changes.outputs.significant }}
should-run: ${{ steps.should-run.outputs.should-run }}
steps:
- uses: actions/checkout@v4
- name: Fetch the base branch
if: ${{ github.event_name == 'pull_request' }}
run: git fetch origin ${{ github.base_ref }} --depth=1
- name: Check for path changes
id: path-changes
shell: bash
run: |
set -euo pipefail
# Manual runs or push should run all tests.
if [[ ${{ github.event_name }} != 'pull_request' ]]; then
echo "significant=true" >> $GITHUB_OUTPUT
exit 0
fi
changed_files=$(git diff --name-only origin/${{ github.base_ref }})
echo "$changed_files"
if grep -Evq '^README.md|^public/kcl-samples/|^rust/kcl-lib/tests/kcl_samples/' <<< "$changed_files" ; then
echo "significant=true" >> $GITHUB_OUTPUT
else
echo "significant=false" >> $GITHUB_OUTPUT
fi
- name: Should run
id: should-run
shell: bash
run: |
set -euo pipefail
# We should run when this is a scheduled run or if there are
# significant changes in the diff.
if [[ ${{ github.event_name }} == 'schedule' || ${{ steps.path-changes.outputs.significant }} == 'true' ]]; then
echo "should-run=true" >> $GITHUB_OUTPUT
else
echo "should-run=false" >> $GITHUB_OUTPUT
fi
- name: Display conditions
shell: bash
run: |
# For debugging purposes
set -euo pipefail
echo "GITHUB_REF: $GITHUB_REF"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm: prepare-wasm:
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner # separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: conditions
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
- id: filter - id: filter
if: needs.conditions.outputs.should-run == 'true'
name: Check for Rust changes name: Check for Rust changes
uses: dorny/paths-filter@v3 uses: dorny/paths-filter@v3
with: with:
@ -93,18 +33,16 @@ jobs:
- 'rust/**' - 'rust/**'
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
if: needs.conditions.outputs.should-run == 'true'
run: npm install run: npm install
- name: Download Wasm Cache - name: Download Wasm Cache
id: download-wasm id: download-wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }} if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7 uses: dawidd6/action-download-artifact@v7
continue-on-error: true continue-on-error: true
with: with:
@ -116,7 +54,6 @@ jobs:
- name: Build WASM condition - name: Build WASM condition
id: wasm id: wasm
if: needs.conditions.outputs.should-run == 'true'
run: | run: |
set -euox pipefail set -euox pipefail
# Build wasm if this is a scheduled run, there are Rust changes, or # Build wasm if this is a scheduled run, there are Rust changes, or
@ -128,35 +65,34 @@ jobs:
fi fi
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust - name: Install rust
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: false # Configured below. cache: false # Configured below.
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc - uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
with: with:
tool: wasm-pack tool: wasm-pack
- name: Rust Cache - name: Rust Cache
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: './rust' workspaces: './rust'
- name: Build Wasm - name: Build Wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash shell: bash
run: npm run build:wasm run: npm run build:wasm
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
name: prepared-wasm name: prepared-wasm
path: | path: |
@ -165,10 +101,9 @@ jobs:
snapshots: snapshots:
name: playwright:snapshots:ubuntu name: playwright:snapshots:ubuntu
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: [conditions, prepare-wasm] needs: [prepare-wasm]
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v1
if: needs.conditions.outputs.should-run == 'true'
id: app-token id: app-token
with: with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }} app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
@ -176,16 +111,13 @@ jobs:
owner: ${{ github.repository_owner }} owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
token: ${{ steps.app-token.outputs.token }} token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm name: prepared-wasm
- name: Copy prepared wasm - name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: | run: |
ls -R prepared-wasm ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -193,18 +125,15 @@ jobs:
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
id: deps-install id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: npm install run: npm install
- name: Cache Playwright Browsers - name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
@ -212,15 +141,12 @@ jobs:
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: npm run playwright install --with-deps run: npm run playwright install --with-deps
- name: build web - name: build web
if: needs.conditions.outputs.should-run == 'true'
run: npm run tronb:vite:dev run: npm run tronb:vite:dev
- name: Run ubuntu/chrome snapshots - name: Run ubuntu/chrome snapshots
if: needs.conditions.outputs.should-run == 'true'
uses: nick-fields/retry@v3.0.2 uses: nick-fields/retry@v3.0.2
with: with:
shell: bash shell: bash
@ -236,7 +162,7 @@ jobs:
TARGET: web TARGET: web
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
with: with:
name: playwright-report-ubuntu-snapshot-${{ github.sha }} name: playwright-report-ubuntu-snapshot-${{ github.sha }}
path: playwright-report/ path: playwright-report/
@ -245,7 +171,7 @@ jobs:
overwrite: true overwrite: true
- name: Check for changes - name: Check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }} if: ${{ github.ref != 'refs/heads/main' }}
shell: bash shell: bash
id: git-check id: git-check
run: | run: |
@ -257,7 +183,7 @@ jobs:
- name: Commit changes, if any - name: Commit changes, if any
# TODO: find a more reliable way to detect visual changes # TODO: find a more reliable way to detect visual changes
if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }} if: ${{ false && steps.git-check.outputs.modified == 'true' }}
shell: bash shell: bash
run: | run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -272,7 +198,7 @@ jobs:
git push origin ${{ github.head_ref }} git push origin ${{ github.head_ref }}
electron: electron:
needs: [conditions, prepare-wasm] needs: [prepare-wasm]
timeout-minutes: 60 timeout-minutes: 60
env: env:
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }} OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
@ -315,14 +241,11 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm name: prepared-wasm
- name: Copy prepared wasm - name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: | run: |
ls -R prepared-wasm ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -330,18 +253,15 @@ jobs:
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
id: deps-install id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: npm install run: npm install
- name: Cache Playwright Browsers - name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
@ -349,22 +269,20 @@ jobs:
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: npm run playwright install --with-deps run: npm run playwright install --with-deps
- name: Build web - name: Build web
if: needs.conditions.outputs.should-run == 'true'
run: npm run tronb:vite:dev run: npm run tronb:vite:dev
- name: Start Vector - name: Start Vector
if: ${{ needs.conditions.outputs.should-run == 'true' && !contains(matrix.os, 'windows') }} if: ${{ !contains(matrix.os, 'windows') }}
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
env: env:
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }} GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
OS_NAME: ${{ env.OS_NAME }} OS_NAME: ${{ env.OS_NAME }}
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true continue-on-error: true
with: with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
@ -372,7 +290,7 @@ jobs:
- name: Run playwright/electron flow (with retries) - name: Run playwright/electron flow (with retries)
id: retry id: retry
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }} if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2 uses: nick-fields/retry@v3.0.2
with: with:
shell: bash shell: bash
@ -389,7 +307,7 @@ jobs:
TARGET: desktop TARGET: desktop
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }} if: always()
with: with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
@ -398,7 +316,7 @@ jobs:
overwrite: true overwrite: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }} if: always()
with: with:
name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/

View File

@ -213,7 +213,13 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run playwright install chromium --with-deps run: npm run playwright install chromium --with-deps
- name: run unit tests for kcl samples - name: Download internal KCL samples
run: git clone --depth=1 https://x-access-token:${{ secrets.GH_PAT_KCL_SAMPLES_INTERNAL }}@github.com/KittyCAD/kcl-samples-internal public/kcl-samples/internal
- name: Regenerate KCL samples manifest
run: cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
- name: Check public and internal KCL samples
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run test:unit:kcl-samples run: npm run test:unit:kcl-samples
env: env:

View File

@ -1,39 +0,0 @@
name: tag-nightly
permissions:
contents: write
on:
schedule:
- cron: '0 4 * * *'
# Daily at 04:00 AM UTC
# Will checkout the last commit from the default branch (main as of 2023-10-04)
jobs:
tag-nightly:
runs-on: ubuntu-22.04
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- run: npm install
- name: Push tag
run: |
VERSION_NO_V=$(date +'%-y.%-m.%-d')
TAG="nightly-v$VERSION_NO_V"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git tag $TAG
git push origin tag $TAG

View File

@ -23,6 +23,7 @@ endif
install: node_modules/.package-lock.json $(CARGO) $(WASM_PACK) ## Install dependencies install: node_modules/.package-lock.json $(CARGO) $(WASM_PACK) ## Install dependencies
node_modules/.package-lock.json: package.json package-lock.json node_modules/.package-lock.json: package.json package-lock.json
npm prune
npm install npm install
$(CARGO): $(CARGO):
@ -43,15 +44,15 @@ endif
# BUILD # BUILD
CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*) CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*)
KCL_SOURCES := $(wildcard public/kcl-samples/**/*.kcl)
RUST_SOURCES := $(wildcard rust/**/*.rs) RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx) REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts) TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx) VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
.PHONY: build .PHONY: build
build: install public/kcl_wasm_lib_bg.wasm .vite/build/main.js build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES) public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES)
ifdef WINDOWS ifdef WINDOWS
@ -60,6 +61,9 @@ else
npm run build:wasm:dev npm run build:wasm:dev
endif endif
public/kcl-samples/manifest.json: $(KCL_SOURCES)
cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES) .vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
npm run tronb:vite:dev npm run tronb:vite:dev
@ -107,8 +111,10 @@ test: test-unit test-e2e
.PHONY: test-unit .PHONY: test-unit
test-unit: install ## Run the unit tests test-unit: install ## Run the unit tests
npm run test:rust npm run test:rust
npm run test:unit:components
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 ) @ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
npm run test:unit npm run test:unit
npm run test:unit:kcl-samples
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: test-e2e-$(TARGET) test-e2e: test-e2e-$(TARGET)

32
docs/kcl-lang/arrays.md Normal file
View File

@ -0,0 +1,32 @@
---
title: "Arrays and ranges"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
Arrays are sequences of values.
Arrays can be written out as *array literals* using a sequence of expressions surrounded by square brackets, e.g., `['hello', 'world']` is an array of strings, `[x, x + 1, x + 2]` is an array of numbers (assuming `x` is a number), `[]` is an empty array, and `['hello', 42, true]` is a mixed array.
A value in an array can be accessed by indexing using square brackets where the index is a number, for example, `arr[0]`, `arr[42]`, `arr[i]` (where `arr` is an array and `i` is a (whole) number).
There are some useful functions for working with arrays in the standard library, see [std::array](/docs/kcl-std/modules/std-array) for details.
## Array types
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
## Ranges
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples:
```kcl,norun
[0..3] // [0, 1, 2, 3]
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
x = 2
[x..x+1] // [2, 3]
```
The units of the start and end numbers must be the same and the result inherits those units.

View File

@ -0,0 +1,99 @@
---
title: "Importing geometry from other CAD systems"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
`import` can also be used to import files from other CAD systems. The format of the statement is the
same as for KCL files. You can only import the whole file, not items from it. E.g.,
```norun
import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object.
```
```kcl
import "tests/inputs/cube.sldprt" as cube
// Use `cube` just like a KCL object.
```
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
unit of measurement is millimeters. Alternatively you may specify the unit
by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
```kcl
@(lengthUnit = ft, coords = opengl)
import "tests/inputs/cube.obj"
```
When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when
using the native Design Studio, not in the browser.
### Supported values
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
'+' support customising the length unit and coordinate system).
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
Coordinate systems:
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
- `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left
---
## Performance deepdive for foreignfile imports
Parallelized foreignfile imports now let you overlap file reads, initialization,
and rendering. To maximize throughput, you need to understand the three distinct
stages—reading, initializing (background render start), and invocation (blocking)
—and structure your code to defer blocking operations until the end.
### Foreign import execution stages
1. **Import (Read / Initialization) Stage**
```kcl
import "tests/inputs/cube.step" as cube
```
- Reads the file from disk and makes its API available.
- Starts engine rendering but **does not block** your script.
- This kickstarts the render pipeline while you keep executing other code.
2. **Invocation (Blocking) Stage**
```kcl
import "tests/inputs/cube.step" as cube
cube
|> translate(z=10) // ← blocks here only
```
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
### Best practices
#### 1. Defer blocking calls
```kcl
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
// --- perform other operations and calculations here ---
cube
|> translate(z=10) // 2) Blocks only here
```
#### 2. Split heavy work into separate modules
Place computationally expensive or IOheavy work into its own module so it can render in parallel while `main.kcl` continues.
#### Future improvements
Upcoming releases will autoanalyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.

View File

@ -5,7 +5,7 @@ layout: manual
--- ---
This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
things in a more tutorial fashion. things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
## Topics ## Topics
@ -14,7 +14,9 @@ things in a more tutorial fashion.
* [Values and types](/docs/kcl-lang/types) * [Values and types](/docs/kcl-lang/types)
* [Numeric types and units](/docs/kcl-lang/numeric) * [Numeric types and units](/docs/kcl-lang/numeric)
* [Functions](/docs/kcl-lang/functions) * [Functions](/docs/kcl-lang/functions)
* [Projects, modules, and imports](/docs/kcl-lang/modules) * [Arrays and ranges](/docs/kcl-lang/arrays)
* [Projects and modules](/docs/kcl-lang/modules)
* [Attributes](/docs/kcl-lang/attributes) * [Attributes](/docs/kcl-lang/attributes)
* [Importing geometry from other CAD systems](/docs/kcl-lang/foreign-imports)
* [Settings](/docs/kcl-lang/settings) * [Settings](/docs/kcl-lang/settings)
* [Known Issues](/docs/kcl-lang/known-issues) * [Known Issues](/docs/kcl-lang/known-issues)

View File

@ -1,5 +1,5 @@
--- ---
title: "Modules" title: "Projects and modules"
excerpt: "Documentation of the KCL language for the Zoo Design Studio." excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---
@ -264,102 +264,3 @@ cube
clone(cube) clone(cube)
|> translate(x=20) |> translate(x=20)
``` ```
---
## Importing files from other CAD systems
`import` can also be used to import files from other CAD systems. The format of the statement is the
same as for KCL files. You can only import the whole file, not items from it. E.g.,
```norun
import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object.
```
```kcl
import "tests/inputs/cube.sldprt" as cube
// Use `cube` just like a KCL object.
```
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
unit of measurement is millimeters. Alternatively you may specify the unit
by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
```kcl
@(lengthUnit = ft, coords = opengl)
import "tests/inputs/cube.obj"
```
When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when
using the native Design Studio, not in the browser.
### Supported values
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
'+' support customising the length unit and coordinate system).
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
Coordinate systems:
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
- `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left
---
## Performance deepdive for foreignfile imports
Parallelized foreignfile imports now let you overlap file reads, initialization,
and rendering. To maximize throughput, you need to understand the three distinct
stages—reading, initializing (background render start), and invocation (blocking)
—and structure your code to defer blocking operations until the end.
### Foreign import execution stages
1. **Import (Read / Initialization) Stage**
```kcl
import "tests/inputs/cube.step" as cube
```
- Reads the file from disk and makes its API available.
- Starts engine rendering but **does not block** your script.
- This kickstarts the render pipeline while you keep executing other code.
2. **Invocation (Blocking) Stage**
```kcl
import "tests/inputs/cube.step" as cube
cube
|> translate(z=10) // ← blocks here only
```
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
### Best practices
#### 1. Defer blocking calls
```kcl
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
// --- perform other operations and calculations here ---
cube
|> translate(z=10) // 2) Blocks only here
```
#### 2. Split heavy work into separate modules
Place computationally expensive or IOheavy work into its own module so it can render in parallel while `main.kcl` continues.
#### Future improvements
Upcoming releases will autoanalyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.

View File

@ -28,6 +28,8 @@ Any of the suffixes described above can be used meaning that values with that ty
You can also use `number(Length)`, `number(Angle)`, or `number(Count)`. These types mean a number with any length, angle, or unitless (count) units, respectively (note that `number(_)` and `number(Count)` are equivalent since there is only one kind of unitless-ness). You can also use `number(Length)`, `number(Angle)`, or `number(Count)`. These types mean a number with any length, angle, or unitless (count) units, respectively (note that `number(_)` and `number(Count)` are equivalent since there is only one kind of unitless-ness).
Using just `number` means accepting any kind of number, even where the units are unknown by KCL.
## Function calls ## Function calls

View File

@ -19,18 +19,6 @@ myBool = false
Currently you cannot redeclare a constant. Currently you cannot redeclare a constant.
## Arrays
An array is defined with `[]` braces. What is inside the brackets can
be of any type. For example, the following is completely valid:
```
myArray = ["thing", 2, false]
```
If you want to get a value from an array you can use the index like so:
`myArray[0]`.
## Objects ## Objects
@ -40,8 +28,8 @@ An object is defined with `{}` braces. Here is an example object:
myObj = { a = 0, b = "thing" } myObj = { a = 0, b = "thing" }
``` ```
We support two different ways of getting properties from objects, you can call To get the property of an object, you can call `myObj.a`, which in the above
`myObj.a` or `myObj["a"]` both work. example returns 0.
## `ImportedGeometry` ## `ImportedGeometry`

View File

@ -22,14 +22,14 @@ This will work on any solid, including extruded solids, revolved solids, and she
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid(s) whose appearance is being set | Yes | | `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid(s) whose appearance is being set | Yes |
| `color` | [`string`](/docs/kcl-std/types/std-types-string) | Color of the new material, a hex string like '#ff0000' | Yes | | `color` | [`string`](/docs/kcl-std/types/std-types-string) | Color of the new material, a hex string like '#ff0000' | Yes |
| `metalness` | [`number`](/docs/kcl-std/types/std-types-number) | Metalness of the new material, a percentage like 95.7. | No | | `metalness` | [`number`](/docs/kcl-std/types/std-types-number) | Metalness of the new material, a percentage like 95.7. | No |
| `roughness` | [`number`](/docs/kcl-std/types/std-types-number) | Roughness of the new material, a percentage like 95.7. | No | | `roughness` | [`number`](/docs/kcl-std/types/std-types-number) | Roughness of the new material, a percentage like 95.7. | No |
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid or an imported geometry.
### Examples ### Examples

View File

@ -11,7 +11,13 @@ The value of `pi`, Archimedes constant (π).
PI: number(_?) = 3.14159265358979323846264338327950288_? PI: number(_?) = 3.14159265358979323846264338327950288_?
``` ```
`PI` is a number and is technically a ratio, so you might expect it to have type `number(_)`.
However, `PI` is nearly always used for converting between different units - usually degrees to or
from radians. Therefore, `PI` is treated a bit specially by KCL and always has unknown units. This
means that if you use `PI`, you will need to give KCL some extra information about the units of numbers.
Usually you should use type ascription on the result of calculations, e.g., `(2 * PI): number(rad)`.
You might prefer to use `units::toRadians` or `units::toDegrees` to convert between angles with
different units.
### Examples ### Examples

View File

@ -26,7 +26,7 @@ You can provide more than one sketch to extrude, and they will all be extruded i
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch or sketches should be extruded | Yes | | `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch or sketches should be extruded | Yes |
| `length` | [`number`](/docs/kcl-std/types/std-types-number) | How far to extrude the given sketches | Yes | | `length` | [`number`](/docs/kcl-std/types/std-types-number) | How far to extrude the given sketches | Yes |
| `symmetric` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the extrusion will happen symmetrically around the sketch. Otherwise, the | No | | `symmetric` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch. | No |
| `bidirectionalLength` | [`number`](/docs/kcl-std/types/std-types-number) | If specified, will also extrude in the opposite direction to 'distance' to the specified distance. If 'symmetric' is true, this value is ignored. | No | | `bidirectionalLength` | [`number`](/docs/kcl-std/types/std-types-number) | If specified, will also extrude in the opposite direction to 'distance' to the specified distance. If 'symmetric' is true, this value is ignored. | No |
| `tagStart` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the start of the extrusion, i.e. the original sketch | No | | `tagStart` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the start of the extrusion, i.e. the original sketch | No |
| `tagEnd` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch | No | | `tagEnd` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch | No |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
---
title: "legAngX"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for x."
layout: manual
---
Compute the angle of the given leg for x.
```kcl
legAngX(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngX(hypotenuse = 5, leg = 3)
```

View File

@ -0,0 +1,38 @@
---
title: "legAngY"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for y."
layout: manual
---
Compute the angle of the given leg for y.
```kcl
legAngY(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngY(hypotenuse = 5, leg = 3)
```

View File

@ -0,0 +1,38 @@
---
title: "legLen"
subtitle: "Function in std::math"
excerpt: "Compute the length of the given leg."
layout: manual
---
Compute the length of the given leg.
```kcl
legLen(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legLen(hypotenuse = 5, leg = 3)
```

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to centimeters from its current units. Convert a number to centimeters from its current units.
```kcl ```kcl
units::toCentimeters(@num: number(cm)): number(cm) units::toCentimeters(@num: number(Length)): number(cm)
``` ```
@ -17,7 +17,7 @@ units::toCentimeters(@num: number(cm)): number(cm)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(cm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Converts a number to degrees from its current units. Converts a number to degrees from its current units.
```kcl ```kcl
units::toDegrees(@num: number(deg)): number(deg) units::toDegrees(@num: number(Angle)): number(deg)
``` ```
@ -17,7 +17,7 @@ units::toDegrees(@num: number(deg)): number(deg)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(deg)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to feet from its current units. Convert a number to feet from its current units.
```kcl ```kcl
units::toFeet(@num: number(ft)): number(ft) units::toFeet(@num: number(Length)): number(ft)
``` ```
@ -17,7 +17,7 @@ units::toFeet(@num: number(ft)): number(ft)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(ft)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to inches from its current units. Convert a number to inches from its current units.
```kcl ```kcl
units::toInches(@num: number(in)): number(in) units::toInches(@num: number(Length)): number(in)
``` ```
@ -17,7 +17,7 @@ units::toInches(@num: number(in)): number(in)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(in)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to meters from its current units. Convert a number to meters from its current units.
```kcl ```kcl
units::toMeters(@num: number(m)): number(m) units::toMeters(@num: number(Length)): number(m)
``` ```
@ -17,7 +17,7 @@ units::toMeters(@num: number(m)): number(m)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(m)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to millimeters from its current units. Convert a number to millimeters from its current units.
```kcl ```kcl
units::toMillimeters(@num: number(mm)): number(mm) units::toMillimeters(@num: number(Length)): number(mm)
``` ```
@ -17,7 +17,7 @@ units::toMillimeters(@num: number(mm)): number(mm)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(mm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Converts a number to radians from its current units. Converts a number to radians from its current units.
```kcl ```kcl
units::toRadians(@num: number(rad)): number(rad) units::toRadians(@num: number(Angle)): number(rad)
``` ```
@ -17,7 +17,7 @@ units::toRadians(@num: number(rad)): number(rad)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(rad)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Converts a number to yards from its current units. Converts a number to yards from its current units.
```kcl ```kcl
units::toYards(@num: number(yd)): number(yd) units::toYards(@num: number(Length)): number(yd)
``` ```
@ -17,7 +17,7 @@ units::toYards(@num: number(yd)): number(yd)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(yd)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -12,15 +12,15 @@ layout: manual
* [`appearance`](/docs/kcl-std/appearance) * [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert) * [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs) * [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/clone) * [`clone`](/docs/kcl-std/functions/std-clone)
* [`helix`](/docs/kcl-std/functions/std-helix) * [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane) * [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d) * [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
* [**std::array**](/docs/kcl-std/modules/std-array) * [**std::array**](/docs/kcl-std/modules/std-array)
* [`map`](/docs/kcl-std/map) * [`map`](/docs/kcl-std/functions/std-array-map)
* [`pop`](/docs/kcl-std/pop) * [`pop`](/docs/kcl-std/functions/std-array-pop)
* [`push`](/docs/kcl-std/push) * [`push`](/docs/kcl-std/functions/std-array-push)
* [`reduce`](/docs/kcl-std/reduce) * [`reduce`](/docs/kcl-std/functions/std-array-reduce)
* [**std::math**](/docs/kcl-std/modules/std-math) * [**std::math**](/docs/kcl-std/modules/std-math)
* [`abs`](/docs/kcl-std/functions/std-math-abs) * [`abs`](/docs/kcl-std/functions/std-math-abs)
* [`acos`](/docs/kcl-std/functions/std-math-acos) * [`acos`](/docs/kcl-std/functions/std-math-acos)
@ -30,9 +30,9 @@ layout: manual
* [`ceil`](/docs/kcl-std/functions/std-math-ceil) * [`ceil`](/docs/kcl-std/functions/std-math-ceil)
* [`cos`](/docs/kcl-std/functions/std-math-cos) * [`cos`](/docs/kcl-std/functions/std-math-cos)
* [`floor`](/docs/kcl-std/functions/std-math-floor) * [`floor`](/docs/kcl-std/functions/std-math-floor)
* [`legAngX`](/docs/kcl-std/legAngX) * [`legAngX`](/docs/kcl-std/functions/std-math-legAngX)
* [`legAngY`](/docs/kcl-std/legAngY) * [`legAngY`](/docs/kcl-std/functions/std-math-legAngY)
* [`legLen`](/docs/kcl-std/legLen) * [`legLen`](/docs/kcl-std/functions/std-math-legLen)
* [`ln`](/docs/kcl-std/functions/std-math-ln) * [`ln`](/docs/kcl-std/functions/std-math-ln)
* [`log`](/docs/kcl-std/functions/std-math-log) * [`log`](/docs/kcl-std/functions/std-math-log)
* [`log10`](/docs/kcl-std/functions/std-math-log10) * [`log10`](/docs/kcl-std/functions/std-math-log10)
@ -140,12 +140,13 @@ See also the [types overview](/docs/kcl-lang/types)
* [**Primitive types**](/docs/kcl-lang/types) * [**Primitive types**](/docs/kcl-lang/types)
* [`End`](/docs/kcl-lang/types#End) * [`End`](/docs/kcl-lang/types#End)
* [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) * [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
* [`Start`](/docs/kcl-lang/types#Start) * [`Start`](/docs/kcl-lang/types#Start)
* [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) * [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator)
* [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) * [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier)
* [`any`](/docs/kcl-std/types/std-types-any) * [`any`](/docs/kcl-std/types/std-types-any)
* [`bool`](/docs/kcl-std/types/std-types-bool) * [`bool`](/docs/kcl-std/types/std-types-bool)
* [`fn`](/docs/kcl-std/types/std-types-fn)
* [`number`](/docs/kcl-std/types/std-types-number) * [`number`](/docs/kcl-std/types/std-types-number)
* [`string`](/docs/kcl-std/types/std-types-string) * [`string`](/docs/kcl-std/types/std-types-string)
* [`tag`](/docs/kcl-std/types/std-types-tag) * [`tag`](/docs/kcl-std/types/std-types-tag)

View File

@ -1,38 +0,0 @@
---
title: "legAngX"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for x."
layout: manual
---
Compute the angle of the given leg for x.
```kcl
legAngX(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngX(hypotenuse = 5, leg = 3)
```

View File

@ -1,38 +0,0 @@
---
title: "legAngY"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for y."
layout: manual
---
Compute the angle of the given leg for y.
```kcl
legAngY(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngY(hypotenuse = 5, leg = 3)
```

View File

@ -1,38 +0,0 @@
---
title: "legLen"
subtitle: "Function in std::math"
excerpt: "Compute the length of the given leg."
layout: manual
---
Compute the length of the given leg.
```kcl
legLen(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legLen(hypotenuse = 5, leg = 3)
```

View File

@ -12,8 +12,8 @@ Functions for manipulating arrays of values.
## Functions and constants ## Functions and constants
* [`map`](/docs/kcl-std/map) * [`map`](/docs/kcl-std/functions/std-array-map)
* [`pop`](/docs/kcl-std/pop) * [`pop`](/docs/kcl-std/functions/std-array-pop)
* [`push`](/docs/kcl-std/push) * [`push`](/docs/kcl-std/functions/std-array-push)
* [`reduce`](/docs/kcl-std/reduce) * [`reduce`](/docs/kcl-std/functions/std-array-reduce)

View File

@ -23,9 +23,9 @@ Functions for mathematical operations and some useful constants.
* [`ceil`](/docs/kcl-std/functions/std-math-ceil) * [`ceil`](/docs/kcl-std/functions/std-math-ceil)
* [`cos`](/docs/kcl-std/functions/std-math-cos) * [`cos`](/docs/kcl-std/functions/std-math-cos)
* [`floor`](/docs/kcl-std/functions/std-math-floor) * [`floor`](/docs/kcl-std/functions/std-math-floor)
* [`legAngX`](/docs/kcl-std/legAngX) * [`legAngX`](/docs/kcl-std/functions/std-math-legAngX)
* [`legAngY`](/docs/kcl-std/legAngY) * [`legAngY`](/docs/kcl-std/functions/std-math-legAngY)
* [`legLen`](/docs/kcl-std/legLen) * [`legLen`](/docs/kcl-std/functions/std-math-legLen)
* [`ln`](/docs/kcl-std/functions/std-math-ln) * [`ln`](/docs/kcl-std/functions/std-math-ln)
* [`log`](/docs/kcl-std/functions/std-math-log) * [`log`](/docs/kcl-std/functions/std-math-log)
* [`log10`](/docs/kcl-std/functions/std-math-log10) * [`log10`](/docs/kcl-std/functions/std-math-log10)

View File

@ -18,6 +18,7 @@ Types can (optionally) be used to describe a function's arguments and returned v
* [`Edge`](/docs/kcl-std/types/std-types-Edge) * [`Edge`](/docs/kcl-std/types/std-types-Edge)
* [`Face`](/docs/kcl-std/types/std-types-Face) * [`Face`](/docs/kcl-std/types/std-types-Face)
* [`Helix`](/docs/kcl-std/types/std-types-Helix) * [`Helix`](/docs/kcl-std/types/std-types-Helix)
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
* [`Plane`](/docs/kcl-std/types/std-types-Plane) * [`Plane`](/docs/kcl-std/types/std-types-Plane)
* [`Point2d`](/docs/kcl-std/types/std-types-Point2d) * [`Point2d`](/docs/kcl-std/types/std-types-Point2d)
* [`Point3d`](/docs/kcl-std/types/std-types-Point3d) * [`Point3d`](/docs/kcl-std/types/std-types-Point3d)
@ -25,6 +26,7 @@ Types can (optionally) be used to describe a function's arguments and returned v
* [`Solid`](/docs/kcl-std/types/std-types-Solid) * [`Solid`](/docs/kcl-std/types/std-types-Solid)
* [`any`](/docs/kcl-std/types/std-types-any) * [`any`](/docs/kcl-std/types/std-types-any)
* [`bool`](/docs/kcl-std/types/std-types-bool) * [`bool`](/docs/kcl-std/types/std-types-bool)
* [`fn`](/docs/kcl-std/types/std-types-fn)
* [`number`](/docs/kcl-std/types/std-types-number) * [`number`](/docs/kcl-std/types/std-types-number)
* [`string`](/docs/kcl-std/types/std-types-string) * [`string`](/docs/kcl-std/types/std-types-string)
* [`tag`](/docs/kcl-std/types/std-types-tag) * [`tag`](/docs/kcl-std/types/std-types-tag)

View File

@ -11,6 +11,8 @@ Contains frequently used constants, functions for interacting with the KittyCAD
The standard library is organised into modules (listed below), but most things are always available in KCL programs. The standard library is organised into modules (listed below), but most things are always available in KCL programs.
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
## Modules ## Modules
* [`array`](/docs/kcl-std/modules/std-array) * [`array`](/docs/kcl-std/modules/std-array)
@ -35,7 +37,7 @@ The standard library is organised into modules (listed below), but most things a
* [`appearance`](/docs/kcl-std/appearance) * [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert) * [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs) * [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/clone) * [`clone`](/docs/kcl-std/functions/std-clone)
* [`helix`](/docs/kcl-std/functions/std-helix) * [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane) * [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d) * [`patternLinear2d`](/docs/kcl-std/patternLinear2d)

View File

@ -43,7 +43,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to rotate. | Yes | | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to rotate. | Yes |
| `roll` | [`number`](/docs/kcl-std/types/std-types-number) | The roll angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | `roll` | [`number`](/docs/kcl-std/types/std-types-number) | The roll angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No |
| `pitch` | [`number`](/docs/kcl-std/types/std-types-number) | The pitch angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | `pitch` | [`number`](/docs/kcl-std/types/std-types-number) | The pitch angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No |
| `yaw` | [`number`](/docs/kcl-std/types/std-types-number) | The yaw angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | `yaw` | [`number`](/docs/kcl-std/types/std-types-number) | The yaw angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No |
@ -53,7 +53,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples ### Examples

View File

@ -29,7 +29,7 @@ If you want to apply the transform in global space, set `global` to `true`. The
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to scale. | Yes | | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to scale. | Yes |
| `x` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the x axis. Default is 1 if not provided. | No | | `x` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the x axis. Default is 1 if not provided. | No |
| `y` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the y axis. Default is 1 if not provided. | No | | `y` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the y axis. Default is 1 if not provided. | No |
| `z` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the z axis. Default is 1 if not provided. | No | | `z` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the z axis. Default is 1 if not provided. | No |
@ -37,7 +37,7 @@ If you want to apply the transform in global space, set `global` to `true`. The
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples ### Examples

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to move. | Yes | | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to move. | Yes |
| `x` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the x axis. Defaults to 0 if not provided. | No | | `x` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the x axis. Defaults to 0 if not provided. | No |
| `y` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the y axis. Defaults to 0 if not provided. | No | | `y` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the y axis. Defaults to 0 if not provided. | No |
| `z` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the z axis. Defaults to 0 if not provided. | No | | `z` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the z axis. Defaults to 0 if not provided. | No |
@ -33,7 +33,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples ### Examples

View File

@ -0,0 +1,13 @@
---
title: "ImportedGeometry"
subtitle: "Type in std::types"
excerpt: "Represents geometry which is defined using some other CAD system and imported into KCL."
layout: manual
---
Represents geometry which is defined using some other CAD system and imported into KCL.

View File

@ -7,8 +7,25 @@ layout: manual
An abstract plane. An abstract plane.
A plane has a position and orientation in space defined by its origin and axes. A plane can be used A plane has a position and orientation in space defined by its origin and axes. A plane is abstract
to sketch on. in the sense that it is not part of the objects being drawn. A plane can be used to sketch on.
A plane can be created in several ways:
- you can use one of the default planes, e.g., `XY`.
- you can use `offsetPlane` to create a new plane offset from an existing one, e.g., `offsetPlane(XY, offset = 150)`.
- you can use negation to create a plane from an existing one which is identical but has an opposite normal
e.g., `-XY`.
- you can define an entirely custom plane, e.g.,
```js
myXY = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
}
```
Any object with appropriate `origin`, `xAxis`, and `yAxis` fields can be used as a plane.

View File

@ -0,0 +1,13 @@
---
title: "fn"
subtitle: "Type in std::types"
excerpt: "The type of any function in KCL."
layout: manual
---
The type of any function in KCL.

View File

@ -549,48 +549,6 @@ extrude002 = extrude(profile002, length = 150)
}) })
}) })
test(
`Network health indicator only appears in modeling view`,
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
// Locators
const projectsHeading = page.getByRole('heading', {
name: 'Projects',
})
const projectLink = page.getByRole('link', { name: 'bracket' })
const networkHealthIndicator = page.getByTestId('network-toggle')
await test.step('Check the home page', async () => {
await expect(projectsHeading).toBeVisible()
await expect(projectLink).toBeVisible()
await expect(networkHealthIndicator).not.toBeVisible()
})
await test.step('Open the project', async () => {
await projectLink.click()
})
await test.step('Check the modeling view', async () => {
await expect(networkHealthIndicator).toBeVisible()
await expect(networkHealthIndicator).toContainText('Problem')
await u.waitForPageLoad()
await expect(networkHealthIndicator).toContainText('Connected')
})
}
)
test(`View gizmo stays visible even when zoomed out all the way`, async ({ test(`View gizmo stays visible even when zoomed out all the way`, async ({
page, page,
homePage, homePage,
@ -841,6 +799,40 @@ washer = extrude(washerSketch, length = thicknessMax)`
await editor.expectEditor.toContain('@settings(defaultLengthUnit = yd)') await editor.expectEditor.toContain('@settings(defaultLengthUnit = yd)')
}) })
}) })
test('Exiting existing sketch without editing should not delete it', async ({
page,
editor,
homePage,
context,
toolbar,
scene,
cmdBar,
}) => {
await context.folderSetupFn(async (dir) => {
const testDir = path.join(dir, 'test')
await fsp.mkdir(testDir, { recursive: true })
await fsp.writeFile(
path.join(testDir, 'main.kcl'),
`s1 = startSketchOn(XY)
|> startProfile(at = [0, 25])
|> xLine(endAbsolute = -15 + 1.5)
s2 = startSketchOn(XY)
|> startProfile(at = [25, 0])
|> yLine(endAbsolute = -15 + 1.5)`,
'utf-8'
)
})
await homePage.openProject('test')
await scene.settled(cmdBar)
await toolbar.waitForFeatureTreeToBeBuilt()
await toolbar.editSketch(1)
await page.waitForTimeout(1000) // Just hang out for a second
await toolbar.exitSketch()
await editor.expectEditor.toContain('s2 = startSketchOn(XY)')
})
}) })
async function clickExportButton(page: Page) { async function clickExportButton(page: Page) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -905,7 +905,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Go into the project that was created from Text to CAD // Go into the project that was created from Text to CAD
await page.getByText(projectName).click() await homePage.openProject(projectName)
await expect(page.getByTestId('app-header-project-name')).toBeVisible() await expect(page.getByTestId('app-header-project-name')).toBeVisible()
await expect(page.getByTestId('app-header-project-name')).toContainText( await expect(page.getByTestId('app-header-project-name')).toContainText(
@ -951,7 +951,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Go into the project that was created from Text to CAD // Go into the project that was created from Text to CAD
await page.getByText(projectName).click() await homePage.openProject(projectName)
await page.getByRole('button', { name: 'Accept' }).click() await page.getByRole('button', { name: 'Accept' }).click()

View File

@ -107,7 +107,6 @@
"check": "biome check ./src ./e2e ./packages/codemirror-lsp-client/src ./rust/kcl-language-server/client/src", "check": "biome check ./src ./e2e ./packages/codemirror-lsp-client/src ./rust/kcl-language-server/client/src",
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh", "fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
"fetch:wasm:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/get-latest-wasm-bundle.ps1", "fetch:wasm:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"", "remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src", "lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src", "lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
@ -123,7 +122,6 @@
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly", "files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "electron-rebuild", "postinstall": "electron-rebuild",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
"tron:start": "electron-forge start", "tron:start": "electron-forge start",
"chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot", "chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot",
"tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development", "tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development",

25
rust/Cargo.lock generated
View File

@ -1815,7 +1815,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.68" version = "0.1.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1826,7 +1826,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-derive-docs" name = "kcl-derive-docs"
version = "0.1.68" version = "0.1.69"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -1845,7 +1845,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
version = "0.1.68" version = "0.1.69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1854,7 +1854,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server" name = "kcl-language-server"
version = "0.2.68" version = "0.2.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1875,7 +1875,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.68" version = "0.1.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1895,7 +1895,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.68" version = "0.2.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1959,6 +1959,7 @@ dependencies = [
"url", "url",
"uuid", "uuid",
"validator", "validator",
"walkdir",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@ -1970,7 +1971,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-python-bindings" name = "kcl-python-bindings"
version = "0.3.68" version = "0.3.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kcl-lib", "kcl-lib",
@ -1985,7 +1986,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.68" version = "0.1.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.32", "hyper 0.14.32",
@ -1998,7 +1999,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-to-core" name = "kcl-to-core"
version = "0.1.68" version = "0.1.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -2012,7 +2013,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-wasm-lib" name = "kcl-wasm-lib"
version = "0.1.68" version = "0.1.69"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bson", "bson",
@ -2042,9 +2043,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad" name = "kittycad"
version = "0.3.36" version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a345fd2a4cb16205f32bd1aa41715045830c59d78c59927fca6580e2a651ac9" checksum = "b48a9698d68c791df76aa020b596c324177a614e38ff3dd67eedd04db76e222f"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",

View File

@ -35,7 +35,7 @@ console_error_panic_hook = "0.1.7"
dashmap = { version = "6.1.0" } dashmap = { version = "6.1.0" }
http = "1" http = "1"
indexmap = "2.9.0" indexmap = "2.9.0"
kittycad = { version = "0.3.36", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.3.37", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.117", features = ["ts-rs", "websocket"] } kittycad-modeling-cmds = { version = "0.2.117", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0" lazy_static = "1.5.0"
miette = "7.5.0" miette = "7.5.0"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.68" version = "0.1.69"
edition = "2021" edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api" repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76" rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-derive-docs" name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.68" version = "0.1.69"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files" description = "A tool for generating tests from a directory of kcl files"
version = "0.1.68" version = "0.1.69"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.68" version = "0.1.69"
edition = "2021" edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server" name = "kcl-language-server"
description = "A language server for KCL." description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.68" version = "0.2.69"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language implementation and tools" description = "KittyCAD Language implementation and tools"
version = "0.2.68" version = "0.2.69"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"
@ -85,6 +85,7 @@ tynm = "0.1.10"
url = { version = "2.5.4", features = ["serde"] } url = { version = "2.5.4", features = ["serde"] }
uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] } uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] }
validator = { version = "0.20.0", features = ["derive"] } validator = { version = "0.20.0", features = ["derive"] }
walkdir = "2.5.0"
web-time = "1.1" web-time = "1.1"
winnow = "=0.6.24" winnow = "=0.6.24"
zip = { workspace = true } zip = { workspace = true }

View File

@ -3,6 +3,7 @@
use kcl_lib::{bust_cache, ExecError, ExecOutcome}; use kcl_lib::{bust_cache, ExecError, ExecOutcome};
use kcmc::{each_cmd as mcmd, ModelingCmd}; use kcmc::{each_cmd as mcmd, ModelingCmd};
use kittycad_modeling_cmds as kcmc; use kittycad_modeling_cmds as kcmc;
use pretty_assertions::assert_eq;
#[derive(Debug)] #[derive(Debug)]
struct Variation<'a> { struct Variation<'a> {
@ -247,8 +248,11 @@ extrude(profile001, length = 100)"#
) )
.await; .await;
result.first().unwrap(); let first = result.first().unwrap();
result.last().unwrap(); let last = result.last().unwrap();
assert!(first.1 == last.1, "The images should be the same");
assert_eq!(first.2, last.2, "The outcomes should be the same");
} }
#[cfg(feature = "artifact-graph")] #[cfg(feature = "artifact-graph")]
@ -550,3 +554,64 @@ extrude(profile001, length = 100)
"The outcomes artifact graphs should be different" "The outcomes artifact graphs should be different"
); );
} }
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_cache_multi_file_same_code_dont_reexecute_settings_only_change() {
let code = r#"import "toBeImported.kcl" as importedCube
importedCube
sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [-134.53, -56.17])
|> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(profile001, length = 100)
sketch003 = startSketchOn(extrude001, face = seg02)
sketch002 = startSketchOn(extrude001, face = seg01)
"#;
let other_file = (
std::path::PathBuf::from("toBeImported.kcl"),
r#"sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [281.54, 305.81])
|> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude(profile001, length = 100)"#
.to_string(),
);
let result = cache_test(
"multi_file_same_code_dont_reexecute_settings_only_change",
vec![
Variation {
code,
other_files: vec![other_file.clone()],
settings: &kcl_lib::ExecutorSettings {
show_grid: false,
..Default::default()
},
},
Variation {
code,
other_files: vec![other_file],
settings: &kcl_lib::ExecutorSettings {
show_grid: true,
..Default::default()
},
},
],
)
.await;
let first = result.first().unwrap();
let last = result.last().unwrap();
assert!(first.1 != last.1, "The images should be different for the grid");
assert_eq!(first.2, last.2, "The outcomes should be the same");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -16,7 +16,7 @@ use crate::{
}; };
// Types with special handling. // Types with special handling.
const SPECIAL_TYPES: [&str; 5] = ["TagDeclarator", "TagIdentifier", "Start", "End", "ImportedGeometry"]; const SPECIAL_TYPES: [&str; 4] = ["TagDeclarator", "TagIdentifier", "Start", "End"];
const TYPE_REWRITES: [(&str, &str); 11] = [ const TYPE_REWRITES: [(&str, &str); 11] = [
("TagNode", "TagDeclarator"), ("TagNode", "TagDeclarator"),
@ -380,6 +380,21 @@ fn generate_function_from_kcl(
.enumerate() .enumerate()
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name)) .filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name))
.collect(); .collect();
let args = function.args.iter().map(|arg| {
let docs = arg.docs.clone();
if let Some(docs) = &docs {
// We deliberately truncate to one line in the template so that if we are using the docs
// from the type, then we only take the summary. However, if there's a newline in the
// arg docs, then they would get truncated unintentionally.
assert!(!docs.contains('\n'), "Arg docs will get truncated");
};
json!({
"name": arg.name,
"type_": arg.ty,
"description": docs.or_else(|| arg.ty.as_ref().and_then(|t| super::docs_for_type(t, kcl_std))).unwrap_or_default(),
"required": arg.kind.required(),
})
}).collect::<Vec<_>>();
let data = json!({ let data = json!({
"name": function.preferred_name, "name": function.preferred_name,
@ -389,14 +404,7 @@ fn generate_function_from_kcl(
"deprecated": function.properties.deprecated, "deprecated": function.properties.deprecated,
"fn_signature": function.preferred_name.clone() + &function.fn_signature(), "fn_signature": function.preferred_name.clone() + &function.fn_signature(),
"examples": examples, "examples": examples,
"args": function.args.iter().map(|arg| { "args": args,
json!({
"name": arg.name,
"type_": arg.ty,
"description": arg.docs.clone().or_else(|| arg.ty.as_ref().and_then(|t| super::docs_for_type(t, kcl_std))).unwrap_or_default(),
"required": arg.kind.required(),
})
}).collect::<Vec<_>>(),
"return_value": function.return_type.as_ref().map(|t| { "return_value": function.return_type.as_ref().map(|t| {
json!({ json!({
"type_": t, "type_": t,
@ -666,6 +674,8 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
if fmt_for_text && ty.starts_with("number") { if fmt_for_text && ty.starts_with("number") {
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)") format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)")
} else if fmt_for_text && ty.starts_with("fn") {
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-fn)")
} else if fmt_for_text && SPECIAL_TYPES.contains(&ty) { } else if fmt_for_text && SPECIAL_TYPES.contains(&ty) {
format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})") format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})")
} else if fmt_for_text && DECLARED_TYPES.contains(&ty) { } else if fmt_for_text && DECLARED_TYPES.contains(&ty) {

View File

@ -626,6 +626,8 @@ impl FnData {
pub(super) fn to_autocomplete_snippet(&self) -> String { pub(super) fn to_autocomplete_snippet(&self) -> String {
if self.name == "loft" { if self.name == "loft" {
return "loft([${0:sketch000}, ${1:sketch001}])".to_owned(); return "loft([${0:sketch000}, ${1:sketch001}])".to_owned();
} else if self.name == "clone" {
return "clone(${0:part001})".to_owned();
} else if self.name == "hole" { } else if self.name == "hole" {
return "hole(${0:holeSketch}, ${1:%})".to_owned(); return "hole(${0:holeSketch}, ${1:%})".to_owned();
} }

View File

@ -11,6 +11,7 @@ use std::{
use anyhow::Result; use anyhow::Result;
use kcl_doc::ModData; use kcl_doc::ModData;
use parse_display::Display;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{ use tower_lsp::lsp_types::{
@ -18,15 +19,27 @@ use tower_lsp::lsp_types::{
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation, MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
}; };
use crate::{ use crate::execution::{types::NumericType, Sketch};
execution::{types::NumericType, Sketch},
std::Primitive,
};
// These types are declared in (KCL) std. // These types are declared in (KCL) std.
const DECLARED_TYPES: [&str; 15] = [ const DECLARED_TYPES: [&str; 17] = [
"any", "number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Edge", "Point2d", "any",
"Point3d", "Axis2d", "Axis3d", "number",
"string",
"tag",
"bool",
"Sketch",
"Solid",
"Plane",
"Helix",
"Face",
"Edge",
"Point2d",
"Point3d",
"Axis2d",
"Axis3d",
"ImportedGeometry",
"fn",
]; ];
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -38,6 +51,21 @@ lazy_static::lazy_static! {
}; };
} }
/// The primitive types that can be used in a KCL file.
#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema, Display)]
#[serde(rename_all = "lowercase")]
#[display(style = "lowercase")]
enum Primitive {
/// A boolean value.
Bool,
/// A number value.
Number,
/// A string value.
String,
/// A uuid value.
Uuid,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -167,6 +195,7 @@ impl StdLibFnArg {
pub fn description(&self, kcl_std: Option<&ModData>) -> Option<String> { pub fn description(&self, kcl_std: Option<&ModData>) -> Option<String> {
// Check if we explicitly gave this stdlib arg a description. // Check if we explicitly gave this stdlib arg a description.
if !self.description.is_empty() { if !self.description.is_empty() {
assert!(!self.description.contains('\n'), "Arg docs will get truncated");
return Some(self.description.clone()); return Some(self.description.clone());
} }
@ -533,8 +562,6 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
fn to_autocomplete_snippet(&self) -> Result<String> { fn to_autocomplete_snippet(&self) -> Result<String> {
if self.name() == "loft" { if self.name() == "loft" {
return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string()); return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string());
} else if self.name() == "clone" {
return Ok("clone(${0:part001})".to_string());
} else if self.name() == "union" { } else if self.name() == "union" {
return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string()); return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string());
} else if self.name() == "subtract" { } else if self.name() == "subtract" {
@ -701,7 +728,7 @@ pub fn get_description_string_from_schema(schema: &schemars::schema::RootSchema)
None None
} }
pub fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> { fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> {
match schema { match schema {
schemars::schema::Schema::Object(o) => { schemars::schema::Schema::Object(o) => {
if o.enum_values.is_some() { if o.enum_values.is_some() {
@ -1007,9 +1034,12 @@ mod tests {
#[test] #[test]
fn get_autocomplete_snippet_map() { fn get_autocomplete_snippet_map() {
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map); let data = kcl_doc::walk_prelude();
let snippet = map_fn.to_autocomplete_snippet().unwrap(); let DocData::Fn(map_fn) = data.find_by_name("map").unwrap() else {
assert_eq!(snippet, r#"map(${0:[0..9]})"#); panic!();
};
let snippet = map_fn.to_autocomplete_snippet();
assert_eq!(snippet, r#"map()"#);
} }
#[test] #[test]
@ -1129,8 +1159,11 @@ mod tests {
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)] #[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_clone() { fn get_autocomplete_snippet_clone() {
let clone_fn: Box<dyn StdLibFn> = Box::new(crate::std::clone::Clone); let data = kcl_doc::walk_prelude();
let snippet = clone_fn.to_autocomplete_snippet().unwrap(); let DocData::Fn(clone_fn) = data.find_by_name("clone").unwrap() else {
panic!();
};
let snippet = clone_fn.to_autocomplete_snippet();
assert_eq!(snippet, r#"clone(${0:part001})"#); assert_eq!(snippet, r#"clone(${0:part001})"#);
} }

View File

@ -559,7 +559,7 @@ impl From<KclError> for pyo3::PyErr {
} }
/// An error which occurred during parsing, etc. /// An error which occurred during parsing, etc.
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)] #[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS, PartialEq, Eq)]
#[ts(export)] #[ts(export)]
pub struct CompilationError { pub struct CompilationError {
#[serde(rename = "sourceRange")] #[serde(rename = "sourceRange")]

View File

@ -118,7 +118,6 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
// We know they have the same imports because the ast is the same. // We know they have the same imports because the ast is the same.
// If we have no imports, we can skip this. // If we have no imports, we can skip this.
if !old.ast.has_import_statements() { if !old.ast.has_import_statements() {
println!("No imports, no need to check.");
return CacheResult::NoAction(reapply_settings); return CacheResult::NoAction(reapply_settings);
} }

View File

@ -278,7 +278,7 @@ impl From<&KclValue> for OpKclValue {
ty: ty.clone(), ty: ty.clone(),
}, },
KclValue::String { value, .. } => Self::String { value: value.clone() }, KclValue::String { value, .. } => Self::String { value: value.clone() },
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
let value = value.iter().map(Self::from).collect(); let value = value.iter().map(Self::from).collect();
Self::Array { value } Self::Array { value }
} }

View File

@ -16,12 +16,13 @@ use crate::{
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo, BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo,
TagIdentifier, TagIdentifier,
}, },
fmt,
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 +708,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 +767,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,
} }
} }
} }
@ -867,11 +877,7 @@ impl Node<MemberExpression> {
source_ranges: vec![self.clone().into()], source_ranges: vec![self.clone().into()],
})) }))
} }
( (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
KclValue::MixedArray { value: arr, .. } | KclValue::HomArray { value: arr, .. },
Property::UInt(index),
_,
) => {
let value_of_arr = arr.get(index); let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr { if let Some(value) = value_of_arr {
Ok(value.to_owned()) Ok(value.to_owned())
@ -882,7 +888,7 @@ impl Node<MemberExpression> {
})) }))
} }
} }
(KclValue::MixedArray { .. } | KclValue::HomArray { .. }, p, _) => { (KclValue::HomArray { .. }, p, _) => {
let t = p.type_name(); let t = p.type_name();
let article = article_for(t); let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
@ -1170,7 +1176,7 @@ impl Node<UnaryExpression> {
}; };
let direction = match direction { let direction = match direction {
KclValue::MixedArray { value: values, meta } => { KclValue::Tuple { value: values, meta } => {
let values = values let values = values
.iter() .iter()
.map(|v| match v { .map(|v| match v {
@ -1183,7 +1189,7 @@ impl Node<UnaryExpression> {
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
KclValue::MixedArray { KclValue::Tuple {
value: values, value: values,
meta: meta.clone(), meta: meta.clone(),
} }
@ -1551,7 +1557,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
} }
} }
} }
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
for v in value { for v in value {
update_memory_for_tags_of_geometry(v, exec_state)?; update_memory_for_tags_of_geometry(v, exec_state)?;
} }
@ -1595,9 +1601,9 @@ impl Node<ArrayExpression> {
results.push(value); results.push(value);
} }
Ok(KclValue::MixedArray { Ok(KclValue::HomArray {
value: results, value: results,
meta: vec![self.into()], ty: RuntimeType::Primitive(PrimitiveType::Any),
}) })
} }
} }
@ -1606,7 +1612,7 @@ impl Node<ArrayRangeExpression> {
#[async_recursion] #[async_recursion]
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let metadata = Metadata::from(&self.start_element); let metadata = Metadata::from(&self.start_element);
let start = ctx let start_val = ctx
.execute_expr( .execute_expr(
&self.start_element, &self.start_element,
exec_state, exec_state,
@ -1615,19 +1621,30 @@ impl Node<ArrayRangeExpression> {
StatementKind::Expression, StatementKind::Expression,
) )
.await?; .await?;
let start = start.as_int().ok_or(KclError::Semantic(KclErrorDetails { let (start, start_ty) = start_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
message: format!("Expected int but found {}", start.human_friendly_type()), message: format!("Expected int but found {}", start_val.human_friendly_type()),
}))?; }))?;
let metadata = Metadata::from(&self.end_element); let metadata = Metadata::from(&self.end_element);
let end = ctx let end_val = ctx
.execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression) .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
.await?; .await?;
let end = end.as_int().ok_or(KclError::Semantic(KclErrorDetails { let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
message: format!("Expected int but found {}", end.human_friendly_type()), message: format!("Expected int but found {}", end_val.human_friendly_type()),
}))?; }))?;
if start_ty != end_ty {
let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
let start = fmt::human_display_number(start.n, start.ty);
let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
let end = fmt::human_display_number(end.n, end.ty);
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Range start and end must be of the same type, but found {start} and {end}"),
}));
}
if end < start { if end < start {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
@ -1644,16 +1661,17 @@ impl Node<ArrayRangeExpression> {
let meta = vec![Metadata { let meta = vec![Metadata {
source_range: self.into(), source_range: self.into(),
}]; }];
Ok(KclValue::MixedArray {
Ok(KclValue::HomArray {
value: range value: range
.into_iter() .into_iter()
.map(|num| KclValue::Number { .map(|num| KclValue::Number {
value: num as f64, value: num as f64,
ty: NumericType::count(), ty: start_ty.clone(),
meta: meta.clone(), meta: meta.clone(),
}) })
.collect(), .collect(),
meta, ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)),
}) })
} }
} }
@ -1868,7 +1886,7 @@ fn type_check_params_kw(
arg.value = arg arg.value = arg
.value .value
.coerce( .coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(), &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
exec_state, exec_state,
) )
.map_err(|e| { .map_err(|e| {
@ -1946,7 +1964,8 @@ fn type_check_params_kw(
arg.value = arg arg.value = arg
.value .value
.coerce( .coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(), &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state, exec_state,
) )
.map_err(|_| { .map_err(|_| {
@ -2125,7 +2144,8 @@ impl FunctionSource {
arg.value = arg arg.value = arg
.value .value
.coerce( .coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(), &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state, exec_state,
) )
.map_err(|_| { .map_err(|_| {
@ -2235,9 +2255,10 @@ mod test {
use super::*; use super::*;
use crate::{ use crate::{
exec::UnitType,
execution::{memory::Stack, parse_execute, ContextType}, execution::{memory::Stack, parse_execute, ContextType},
parsing::ast::types::{DefaultParamVal, Identifier, Parameter}, parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
ExecutorSettings, ExecutorSettings, UnitLen,
}; };
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2391,6 +2412,7 @@ p = {
yAxis = { x = 0, y = 1, z = 0 }, yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 } zAxis = { x = 0, y = 0, z = 1 }
}: Plane }: Plane
arr1 = [42]: [number(cm)]
"#; "#;
let result = parse_execute(program).await.unwrap(); let result = parse_execute(program).await.unwrap();
@ -2401,6 +2423,24 @@ p = {
.unwrap(), .unwrap(),
KclValue::Plane { .. } KclValue::Plane { .. }
)); ));
let arr1 = mem
.memory
.get_from("arr1", result.mem_env, SourceRange::default(), 0)
.unwrap();
if let KclValue::HomArray { value, ty } = arr1 {
assert_eq!(value.len(), 1, "Expected Vec with specific length: found {:?}", value);
assert_eq!(*ty, RuntimeType::known_length(UnitLen::Cm));
// Compare, ignoring meta.
if let KclValue::Number { value, ty, .. } = &value[0] {
// Converted from mm to cm.
assert_eq!(*value, 4.2);
assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLen::Cm)));
} else {
panic!("Expected a number; found {:?}", value[0]);
}
} else {
panic!("Expected HomArray; found {arr1:?}");
}
let program = r#" let program = r#"
a = 42: string a = 42: string
@ -2419,6 +2459,28 @@ a = 42: Plane
.unwrap_err() .unwrap_err()
.to_string() .to_string()
.contains("could not coerce number value to type Plane")); .contains("could not coerce number value to type Plane"));
let program = r#"
arr = [0]: [string]
"#;
let result = parse_execute(program).await;
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce array (list) value to type [string]"),
"Expected error but found {err:?}"
);
let program = r#"
mixedArr = [0, "a"]: [number(mm)]
"#;
let result = parse_execute(program).await;
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce array (list) value to type [number(mm)]"),
"Expected error but found {err:?}"
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2638,4 +2700,19 @@ 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();
}
#[tokio::test(flavor = "multi_thread")]
async fn neg_sqrt() {
let ast = r#"bad = sqrt(-2)"#;
let e = parse_execute(ast).await.unwrap_err();
// Make sure we get a useful error message and not an engine error.
assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
}
} }

View File

@ -1239,6 +1239,20 @@ impl Path {
[TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)] [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
} }
/// The path segment start point and its type.
pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
let p = &self.get_base().from;
let ty: NumericType = self.get_base().units.into();
(*p, ty)
}
/// The path segment end point and its type.
pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
let p = &self.get_base().to;
let ty: NumericType = self.get_base().units.into();
(*p, ty)
}
/// Length of this path segment, in cartesian plane. /// Length of this path segment, in cartesian plane.
pub fn length(&self) -> TyF64 { pub fn length(&self) -> TyF64 {
let n = match self { let n = match self {

View File

@ -48,7 +48,7 @@ pub enum KclValue {
#[serde(skip)] #[serde(skip)]
meta: Vec<Metadata>, meta: Vec<Metadata>,
}, },
MixedArray { Tuple {
value: Vec<KclValue>, value: Vec<KclValue>,
#[serde(skip)] #[serde(skip)]
meta: Vec<Metadata>, meta: Vec<Metadata>,
@ -197,7 +197,7 @@ impl From<KclValue> for Vec<SourceRange> {
KclValue::Bool { meta, .. } => to_vec_sr(&meta), KclValue::Bool { meta, .. } => to_vec_sr(&meta),
KclValue::Number { meta, .. } => to_vec_sr(&meta), KclValue::Number { meta, .. } => to_vec_sr(&meta),
KclValue::String { meta, .. } => to_vec_sr(&meta), KclValue::String { meta, .. } => to_vec_sr(&meta),
KclValue::MixedArray { meta, .. } => to_vec_sr(&meta), KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(), KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
KclValue::Object { meta, .. } => to_vec_sr(&meta), KclValue::Object { meta, .. } => to_vec_sr(&meta),
KclValue::Module { meta, .. } => to_vec_sr(&meta), KclValue::Module { meta, .. } => to_vec_sr(&meta),
@ -228,7 +228,7 @@ impl From<&KclValue> for Vec<SourceRange> {
KclValue::Number { meta, .. } => to_vec_sr(meta), KclValue::Number { meta, .. } => to_vec_sr(meta),
KclValue::String { meta, .. } => to_vec_sr(meta), KclValue::String { meta, .. } => to_vec_sr(meta),
KclValue::Uuid { meta, .. } => to_vec_sr(meta), KclValue::Uuid { meta, .. } => to_vec_sr(meta),
KclValue::MixedArray { meta, .. } => to_vec_sr(meta), KclValue::Tuple { meta, .. } => to_vec_sr(meta),
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(), KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
KclValue::Object { meta, .. } => to_vec_sr(meta), KclValue::Object { meta, .. } => to_vec_sr(meta),
KclValue::Module { meta, .. } => to_vec_sr(meta), KclValue::Module { meta, .. } => to_vec_sr(meta),
@ -252,7 +252,7 @@ impl KclValue {
KclValue::Bool { value: _, meta } => meta.clone(), KclValue::Bool { value: _, meta } => meta.clone(),
KclValue::Number { meta, .. } => meta.clone(), KclValue::Number { meta, .. } => meta.clone(),
KclValue::String { value: _, meta } => meta.clone(), KclValue::String { value: _, meta } => meta.clone(),
KclValue::MixedArray { value: _, meta } => meta.clone(), KclValue::Tuple { value: _, meta } => meta.clone(),
KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(), KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
KclValue::Object { value: _, meta } => meta.clone(), KclValue::Object { value: _, meta } => meta.clone(),
KclValue::TagIdentifier(x) => x.meta.clone(), KclValue::TagIdentifier(x) => x.meta.clone(),
@ -307,7 +307,7 @@ impl KclValue {
} => "number(Angle)", } => "number(Angle)",
KclValue::Number { .. } => "number", KclValue::Number { .. } => "number",
KclValue::String { .. } => "string (text)", KclValue::String { .. } => "string (text)",
KclValue::MixedArray { .. } => "mixed array (list)", KclValue::Tuple { .. } => "tuple (list)",
KclValue::HomArray { .. } => "array (list)", KclValue::HomArray { .. } => "array (list)",
KclValue::Object { .. } => "object", KclValue::Object { .. } => "object",
KclValue::Module { .. } => "module", KclValue::Module { .. } => "module",
@ -373,7 +373,7 @@ impl KclValue {
/// Put the point into a KCL value. /// Put the point into a KCL value.
pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self { pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
Self::MixedArray { Self::Tuple {
value: vec![ value: vec![
Self::Number { Self::Number {
value: p[0], value: p[0],
@ -404,6 +404,13 @@ impl KclValue {
} }
} }
pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
match self {
KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, ty.clone())),
_ => None,
}
}
pub fn as_object(&self) -> Option<&KclObjectFields> { pub fn as_object(&self) -> Option<&KclObjectFields> {
if let KclValue::Object { value, meta: _ } = &self { if let KclValue::Object { value, meta: _ } = &self {
Some(value) Some(value)
@ -430,7 +437,7 @@ impl KclValue {
pub fn as_array(&self) -> Option<&[KclValue]> { pub fn as_array(&self) -> Option<&[KclValue]> {
match self { match self {
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => Some(value), KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value),
_ => None, _ => None,
} }
} }
@ -602,7 +609,7 @@ impl KclValue {
KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)), KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)), KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
// TODO better Array and Object stringification // TODO better Array and Object stringification
KclValue::MixedArray { .. } => Some("[...]".to_owned()), KclValue::Tuple { .. } => Some("[...]".to_owned()),
KclValue::HomArray { .. } => Some("[...]".to_owned()), KclValue::HomArray { .. } => Some("[...]".to_owned()),
KclValue::Object { .. } => Some("{ ... }".to_owned()), KclValue::Object { .. } => Some("{ ... }".to_owned()),
KclValue::Module { .. } KclValue::Module { .. }

View File

@ -64,7 +64,7 @@ pub mod typed_path;
pub(crate) mod types; pub(crate) mod types;
/// Outcome of executing a program. This is used in TS. /// Outcome of executing a program. This is used in TS.
#[derive(Debug, Clone, Serialize, ts_rs::TS)] #[derive(Debug, Clone, Serialize, ts_rs::TS, PartialEq)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ExecOutcome { pub struct ExecOutcome {
@ -385,6 +385,7 @@ impl ExecutorContext {
let (ws, _headers) = client let (ws, _headers) = client
.modeling() .modeling()
.commands_ws( .commands_ws(
None,
None, None,
None, None,
if settings.enable_ssao { if settings.enable_ssao {
@ -653,13 +654,21 @@ impl ExecutorContext {
keys.sort(); keys.sort();
for key in keys { for key in keys {
let (_, id, _, _) = &new_universe[key]; let (_, id, _, _) = &new_universe[key];
let old_source = old_state.get_source(*id); if let (Some(source0), Some(source1)) =
let new_source = new_exec_state.get_source(*id); (old_state.get_source(*id), new_exec_state.get_source(*id))
if old_source != new_source { {
if source0.source != source1.source {
clear_scene = true; clear_scene = true;
break; break;
} }
} }
}
if !clear_scene {
// Return early we don't need to clear the scene.
let outcome = old_state.to_exec_outcome(result_env).await;
return Ok(outcome);
}
( (
clear_scene, clear_scene,
@ -1932,7 +1941,7 @@ a = []
notArray = !a"; notArray = !a";
assert_eq!( assert_eq!(
parse_execute(code5).await.unwrap_err().message(), parse_execute(code5).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: mixed array (list)", "Cannot apply unary operator ! to non-boolean value: array (list)",
); );
let code6 = " let code6 = "

View File

@ -28,6 +28,10 @@ pub enum RuntimeType {
} }
impl RuntimeType { impl RuntimeType {
pub fn any() -> Self {
RuntimeType::Primitive(PrimitiveType::Any)
}
pub fn edge() -> Self { pub fn edge() -> Self {
RuntimeType::Primitive(PrimitiveType::Edge) RuntimeType::Primitive(PrimitiveType::Edge)
} }
@ -72,6 +76,10 @@ impl RuntimeType {
RuntimeType::Primitive(PrimitiveType::Tag) RuntimeType::Primitive(PrimitiveType::Tag)
} }
pub fn tag_identifier() -> Self {
RuntimeType::Primitive(PrimitiveType::TagId)
}
pub fn bool() -> Self { pub fn bool() -> Self {
RuntimeType::Primitive(PrimitiveType::Boolean) RuntimeType::Primitive(PrimitiveType::Boolean)
} }
@ -162,13 +170,20 @@ impl RuntimeType {
source_range: SourceRange, source_range: SourceRange,
) -> Result<Self, CompilationError> { ) -> Result<Self, CompilationError> {
Ok(match value { Ok(match value {
AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String), AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean), AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number( AstPrimitiveType::Number(suffix) => {
NumericType::from_parsed(suffix, &exec_state.mod_local.settings), let ty = match suffix {
)), NumericSuffix::None => NumericType::Any,
_ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
};
RuntimeType::Primitive(PrimitiveType::Number(ty))
}
AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?, AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag), AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
}) })
} }
@ -203,7 +218,7 @@ impl RuntimeType {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" or "), .join(" or "),
RuntimeType::Tuple(tys) => format!( RuntimeType::Tuple(tys) => format!(
"an array with values of types ({})", "a tuple with values of types ({})",
tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ") tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
), ),
RuntimeType::Object(_) => format!("an object with fields {}", self), RuntimeType::Object(_) => format!("an object with fields {}", self),
@ -215,6 +230,7 @@ impl RuntimeType {
use RuntimeType::*; use RuntimeType::*;
match (self, sup) { match (self, sup) {
(_, Primitive(PrimitiveType::Any)) => true,
(Primitive(t1), Primitive(t2)) => t1.subtype(t2), (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
(Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2), (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
(Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)), (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
@ -265,7 +281,7 @@ impl RuntimeType {
.map(|t| t.display_multiple()) .map(|t| t.display_multiple())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" or "), .join(" or "),
RuntimeType::Tuple(_) => "arrays".to_owned(), RuntimeType::Tuple(_) => "tuples".to_owned(),
RuntimeType::Object(_) => format!("objects with fields {self}"), RuntimeType::Object(_) => format!("objects with fields {self}"),
} }
} }
@ -282,7 +298,7 @@ impl fmt::Display for RuntimeType {
}, },
RuntimeType::Tuple(ts) => write!( RuntimeType::Tuple(ts) => write!(
f, f,
"[{}]", "({})",
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ") ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
), ),
RuntimeType::Union(ts) => write!( RuntimeType::Union(ts) => write!(
@ -333,10 +349,13 @@ impl ArrayLen {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum PrimitiveType { pub enum PrimitiveType {
Any,
Number(NumericType), Number(NumericType),
String, String,
Boolean, Boolean,
Tag, Tag,
// Annoyingly some functions only want a TagIdentifier, not any kind of tag.
TagId,
Sketch, Sketch,
Solid, Solid,
Plane, Plane,
@ -346,11 +365,13 @@ pub enum PrimitiveType {
Axis2d, Axis2d,
Axis3d, Axis3d,
ImportedGeometry, ImportedGeometry,
Function,
} }
impl PrimitiveType { impl PrimitiveType {
fn display_multiple(&self) -> String { fn display_multiple(&self) -> String {
match self { match self {
PrimitiveType::Any => "any values".to_owned(),
PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"), PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
PrimitiveType::Number(_) => "numbers".to_owned(), PrimitiveType::Number(_) => "numbers".to_owned(),
PrimitiveType::String => "strings".to_owned(), PrimitiveType::String => "strings".to_owned(),
@ -364,13 +385,17 @@ impl PrimitiveType {
PrimitiveType::Axis2d => "2d axes".to_owned(), PrimitiveType::Axis2d => "2d axes".to_owned(),
PrimitiveType::Axis3d => "3d axes".to_owned(), PrimitiveType::Axis3d => "3d axes".to_owned(),
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
PrimitiveType::Function => "functions".to_owned(),
PrimitiveType::Tag => "tags".to_owned(), PrimitiveType::Tag => "tags".to_owned(),
PrimitiveType::TagId => "tag identifiers".to_owned(),
} }
} }
fn subtype(&self, other: &PrimitiveType) -> bool { fn subtype(&self, other: &PrimitiveType) -> bool {
match (self, other) { match (self, other) {
(_, PrimitiveType::Any) => true,
(PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2), (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
(PrimitiveType::TagId, PrimitiveType::Tag) => true,
(t1, t2) => t1 == t2, (t1, t2) => t1 == t2,
} }
} }
@ -379,6 +404,7 @@ impl PrimitiveType {
impl fmt::Display for PrimitiveType { impl fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
PrimitiveType::Any => write!(f, "any"),
PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"), PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"), PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number(default units)"), PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number(default units)"),
@ -386,6 +412,7 @@ impl fmt::Display for PrimitiveType {
PrimitiveType::String => write!(f, "string"), PrimitiveType::String => write!(f, "string"),
PrimitiveType::Boolean => write!(f, "bool"), PrimitiveType::Boolean => write!(f, "bool"),
PrimitiveType::Tag => write!(f, "tag"), PrimitiveType::Tag => write!(f, "tag"),
PrimitiveType::TagId => write!(f, "tag identifier"),
PrimitiveType::Sketch => write!(f, "Sketch"), PrimitiveType::Sketch => write!(f, "Sketch"),
PrimitiveType::Solid => write!(f, "Solid"), PrimitiveType::Solid => write!(f, "Solid"),
PrimitiveType::Plane => write!(f, "Plane"), PrimitiveType::Plane => write!(f, "Plane"),
@ -395,6 +422,7 @@ impl fmt::Display for PrimitiveType {
PrimitiveType::Axis3d => write!(f, "Axis3d"), PrimitiveType::Axis3d => write!(f, "Axis3d"),
PrimitiveType::Helix => write!(f, "Helix"), PrimitiveType::Helix => write!(f, "Helix"),
PrimitiveType::ImportedGeometry => write!(f, "imported geometry"), PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
PrimitiveType::Function => write!(f, "function"),
} }
} }
} }
@ -663,6 +691,7 @@ impl NumericType {
// We don't have enough information to coerce. // We don't have enough information to coerce.
(Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))), (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
(_, Unknown) => Err(val.into()), (_, Unknown) => Err(val.into()),
(Any, _) => Ok(KclValue::Number { (Any, _) => Ok(KclValue::Number {
value: *value, value: *value,
ty: self.clone(), ty: self.clone(),
@ -994,9 +1023,9 @@ impl KclValue {
self_ty.subtype(ty) self_ty.subtype(ty)
} }
/// Coerce `self` to a new value which has `ty` as it's closest supertype. /// Coerce `self` to a new value which has `ty` as its closest supertype.
/// ///
/// If the result is Some, then: /// If the result is Ok, then:
/// - result.principal_type().unwrap().subtype(ty) /// - result.principal_type().unwrap().subtype(ty)
/// ///
/// If self.principal_type() == ty then result == self /// If self.principal_type() == ty then result == self
@ -1016,10 +1045,11 @@ impl KclValue {
exec_state: &mut ExecState, exec_state: &mut ExecState,
) -> Result<KclValue, CoercionError> { ) -> Result<KclValue, CoercionError> {
let value = match self { let value = match self {
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0], KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
_ => self, _ => self,
}; };
match ty { match ty {
PrimitiveType::Any => Ok(value.clone()),
PrimitiveType::Number(ty) => ty.coerce(value), PrimitiveType::Number(ty) => ty.coerce(value),
PrimitiveType::String => match value { PrimitiveType::String => match value {
KclValue::String { .. } => Ok(value.clone()), KclValue::String { .. } => Ok(value.clone()),
@ -1159,6 +1189,14 @@ impl KclValue {
KclValue::ImportedGeometry { .. } => Ok(value.clone()), KclValue::ImportedGeometry { .. } => Ok(value.clone()),
_ => Err(self.into()), _ => Err(self.into()),
}, },
PrimitiveType::Function => match value {
KclValue::Function { .. } => Ok(value.clone()),
_ => Err(self.into()),
},
PrimitiveType::TagId => match value {
KclValue::TagIdentifier { .. } => Ok(value.clone()),
_ => Err(self.into()),
},
PrimitiveType::Tag => match value { PrimitiveType::Tag => match value {
KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => { KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
Ok(value.clone()) Ok(value.clone())
@ -1178,41 +1216,49 @@ impl KclValue {
exec_state: &mut ExecState, exec_state: &mut ExecState,
allow_shrink: bool, allow_shrink: bool,
) -> Result<KclValue, CoercionError> { ) -> Result<KclValue, CoercionError> {
if len.satisfied(1, false).is_some() && self.has_type(ty) {
return Ok(KclValue::HomArray {
value: vec![self.clone()],
ty: ty.clone(),
});
}
match self { match self {
KclValue::HomArray { value, ty: aty } => { KclValue::HomArray { value, ty: aty, .. } => {
let satisfied_len = len.satisfied(value.len(), allow_shrink);
if aty.subtype(ty) { if aty.subtype(ty) {
len.satisfied(value.len(), allow_shrink) // If the element type is a subtype of the target type and
// the length constraint is satisfied, we can just return
// the values unchanged, only adjusting the length. The new
// array element type should preserve its type because the
// target type oftentimes includes an unknown type as a way
// to say that the caller doesn't care.
return satisfied_len
.map(|len| KclValue::HomArray { .map(|len| KclValue::HomArray {
value: value[..len].to_vec(), value: value[..len].to_vec(),
ty: aty.clone(), ty: aty.clone(),
}) })
.ok_or(self.into()) .ok_or(self.into());
} else { }
Err(self.into())
// Ignore the array type, and coerce the elements of the array.
if let Some(satisfied_len) = satisfied_len {
let value_result = value
.iter()
.take(satisfied_len)
.map(|v| v.coerce(ty, exec_state))
.collect::<Result<Vec<_>, _>>();
if let Ok(value) = value_result {
// We were able to coerce all the elements.
return Ok(KclValue::HomArray { value, ty: ty.clone() });
} }
} }
KclValue::MixedArray { value, .. } => {
// Check if we have a nested homogeneous array that we can flatten. // As a last resort, try to flatten the array.
let mut values = Vec::new(); let mut values = Vec::new();
for item in value { for item in value {
if let KclValue::HomArray { if let KclValue::HomArray { value: inner_value, .. } = item {
ty: inner_ty, // Flatten elements.
value: inner_value, for item in inner_value {
} = item values.push(item.coerce(ty, exec_state)?);
{
if inner_ty.subtype(ty) {
values.extend(inner_value.iter().cloned());
} else {
values.push(item.clone());
} }
} else { } else {
values.push(item.clone()); values.push(item.coerce(ty, exec_state)?);
} }
} }
@ -1220,9 +1266,22 @@ impl KclValue {
.satisfied(values.len(), allow_shrink) .satisfied(values.len(), allow_shrink)
.ok_or(CoercionError::from(self))?; .ok_or(CoercionError::from(self))?;
let value = values[..len] assert!(len <= values.len());
values.truncate(len);
Ok(KclValue::HomArray {
value: values,
ty: ty.clone(),
})
}
KclValue::Tuple { value, .. } => {
let len = len
.satisfied(value.len(), allow_shrink)
.ok_or(CoercionError::from(self))?;
let value = value
.iter() .iter()
.map(|v| v.coerce(ty, exec_state)) .map(|item| item.coerce(ty, exec_state))
.take(len)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(KclValue::HomArray { value, ty: ty.clone() }) Ok(KclValue::HomArray { value, ty: ty.clone() })
@ -1231,28 +1290,32 @@ impl KclValue {
value: Vec::new(), value: Vec::new(),
ty: ty.clone(), ty: ty.clone(),
}), }),
_ if len.satisfied(1, false).is_some() => Ok(KclValue::HomArray {
value: vec![self.coerce(ty, exec_state)?],
ty: ty.clone(),
}),
_ => Err(self.into()), _ => Err(self.into()),
} }
} }
fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> { fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
match self { match self {
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => { KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
let mut result = Vec::new(); let mut result = Vec::new();
for (i, t) in tys.iter().enumerate() { for (i, t) in tys.iter().enumerate() {
result.push(value[i].coerce(t, exec_state)?); result.push(value[i].coerce(t, exec_state)?);
} }
Ok(KclValue::MixedArray { Ok(KclValue::Tuple {
value: result, value: result,
meta: Vec::new(), meta: Vec::new(),
}) })
} }
KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::MixedArray { KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
value: Vec::new(), value: Vec::new(),
meta: meta.clone(), meta: meta.clone(),
}), }),
value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::MixedArray { value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::Tuple {
value: vec![value.clone()], value: vec![value.clone()],
meta: Vec::new(), meta: Vec::new(),
}), }),
@ -1312,18 +1375,16 @@ impl KclValue {
KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)), KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)), KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)), KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple( KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?, value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
)), )),
KclValue::HomArray { ty, value, .. } => { KclValue::HomArray { ty, value, .. } => {
Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len()))) Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
} }
KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => { KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)),
Some(RuntimeType::Primitive(PrimitiveType::Tag)) KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
} KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
KclValue::Function { .. } | KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => { KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None,
None
}
} }
} }
} }
@ -1348,7 +1409,7 @@ mod test {
value: "hello".to_owned(), value: "hello".to_owned(),
meta: Vec::new(), meta: Vec::new(),
}, },
KclValue::MixedArray { KclValue::Tuple {
value: Vec::new(), value: Vec::new(),
meta: Vec::new(), meta: Vec::new(),
}, },
@ -1417,6 +1478,26 @@ mod test {
let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1)); let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty); let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
match v {
KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
// These will not get wrapped if possible.
assert_coerce_results(
v,
&aty,
&KclValue::HomArray {
value: vec![],
ty: ty.clone(),
},
&mut exec_state,
);
// Coercing an empty tuple or array to an array of length 1
// should fail.
v.coerce(&aty1, &mut exec_state).unwrap_err();
// Coercing an empty tuple or array to an array that's
// non-empty should fail.
v.coerce(&aty0, &mut exec_state).unwrap_err();
}
_ => {
assert_coerce_results( assert_coerce_results(
v, v,
&aty, &aty,
@ -1450,13 +1531,15 @@ mod test {
assert_coerce_results( assert_coerce_results(
v, v,
&tty, &tty,
&KclValue::MixedArray { &KclValue::Tuple {
value: vec![v.clone()], value: vec![v.clone()],
meta: Vec::new(), meta: Vec::new(),
}, },
&mut exec_state, &mut exec_state,
); );
} }
}
}
for v in &values[1..] { for v in &values[1..] {
// Not a subtype // Not a subtype
@ -1503,7 +1586,7 @@ mod test {
assert_coerce_results( assert_coerce_results(
&none, &none,
&tty, &tty,
&KclValue::MixedArray { &KclValue::Tuple {
value: Vec::new(), value: Vec::new(),
meta: Vec::new(), meta: Vec::new(),
}, },
@ -1634,7 +1717,7 @@ mod test {
], ],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
}; };
let mixed1 = KclValue::MixedArray { let mixed1 = KclValue::Tuple {
value: vec![ value: vec![
KclValue::Number { KclValue::Number {
value: 0.0, value: 0.0,
@ -1649,7 +1732,7 @@ mod test {
], ],
meta: Vec::new(), meta: Vec::new(),
}; };
let mixed2 = KclValue::MixedArray { let mixed2 = KclValue::Tuple {
value: vec![ value: vec![
KclValue::Number { KclValue::Number {
value: 0.0, value: 0.0,
@ -1739,7 +1822,7 @@ mod test {
], ],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
}; };
let mixed0 = KclValue::MixedArray { let mixed0 = KclValue::Tuple {
value: vec![], value: vec![],
meta: Vec::new(), meta: Vec::new(),
}; };
@ -2156,7 +2239,7 @@ d = cos(30)
async fn coerce_nested_array() { async fn coerce_nested_array() {
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
let mixed1 = KclValue::MixedArray { let mixed1 = KclValue::HomArray {
value: vec![ value: vec![
KclValue::Number { KclValue::Number {
value: 0.0, value: 0.0,
@ -2184,7 +2267,7 @@ d = cos(30)
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
}, },
], ],
meta: Vec::new(), ty: RuntimeType::any(),
}; };
// Principal types // Principal types

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

@ -2,8 +2,8 @@ use sha2::{Digest as DigestTrait, Sha256};
use crate::parsing::ast::types::{ use crate::parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryPart, BodyItem, Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryPart, BodyItem,
CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, Identifier, IfExpression, CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, FunctionType, Identifier,
ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty, Parameter, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty, Parameter,
PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, TagDeclarator, Type, TypeDeclaration, PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, TagDeclarator, Type, TypeDeclaration,
UnaryExpression, VariableDeclaration, VariableDeclarator, VariableKind, UnaryExpression, VariableDeclaration, VariableDeclarator, VariableKind,
@ -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(),
} }
} }
} }
@ -225,17 +226,35 @@ impl PrimitiveType {
pub fn compute_digest(&mut self) -> Digest { pub fn compute_digest(&mut self) -> Digest {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
match self { match self {
PrimitiveType::Any => hasher.update(b"any"),
PrimitiveType::Named(id) => hasher.update(id.compute_digest()), PrimitiveType::Named(id) => hasher.update(id.compute_digest()),
PrimitiveType::String => hasher.update(b"string"), PrimitiveType::String => hasher.update(b"string"),
PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()), PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()),
PrimitiveType::Boolean => hasher.update(b"bool"), PrimitiveType::Boolean => hasher.update(b"bool"),
PrimitiveType::Tag => hasher.update(b"tag"), PrimitiveType::Tag => hasher.update(b"tag"),
PrimitiveType::ImportedGeometry => hasher.update(b"ImportedGeometry"),
PrimitiveType::Function(f) => hasher.update(f.compute_digest()),
} }
hasher.finalize().into() hasher.finalize().into()
} }
} }
impl FunctionType {
compute_digest!(|slf, hasher| {
if let Some(u) = &mut slf.unnamed_arg {
hasher.update(u.compute_digest());
}
slf.named_args.iter_mut().for_each(|(a, t)| {
a.compute_digest();
t.compute_digest();
});
if let Some(r) = &mut slf.return_type {
hasher.update(r.compute_digest());
}
});
}
impl Parameter { impl Parameter {
compute_digest!(|slf, hasher| { compute_digest!(|slf, hasher| {
hasher.update(slf.identifier.compute_digest()); hasher.update(slf.identifier.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,
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More