2023-08-25 13:41:04 -07:00
|
|
|
// Clippy's style advice is definitely valuable, but not worth the trouble for
|
|
|
|
// automated enforcement.
|
|
|
|
#![allow(clippy::style)]
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
mod unbox;
|
|
|
|
|
2025-04-24 22:01:27 +12:00
|
|
|
use std::{collections::HashMap, fs};
|
2024-12-13 13:07:52 -06:00
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
use convert_case::Casing;
|
2025-02-25 10:29:59 -06:00
|
|
|
use inflector::{cases::camelcase::to_camel_case, Inflector};
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
use once_cell::sync::Lazy;
|
2025-04-24 22:01:27 +12:00
|
|
|
use proc_macro2::Span;
|
2023-08-25 13:41:04 -07:00
|
|
|
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
use regex::Regex;
|
2023-08-25 13:41:04 -07:00
|
|
|
use serde::Deserialize;
|
|
|
|
use serde_tokenstream::{from_tokenstream, Error};
|
|
|
|
use syn::{
|
|
|
|
parse::{Parse, ParseStream},
|
|
|
|
Attribute, Signature, Visibility,
|
|
|
|
};
|
2024-03-13 12:56:46 -07:00
|
|
|
use unbox::unbox;
|
2023-08-25 13:41:04 -07:00
|
|
|
|
2025-04-24 22:01:27 +12:00
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn stdlib(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
|
|
do_output(do_stdlib(attr.into(), item.into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn for_each_std_mod(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
|
|
do_for_each_std_mod(item.into()).into()
|
|
|
|
}
|
|
|
|
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
/// Describes an argument of a stdlib function.
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct ArgMetadata {
|
|
|
|
/// Docs for the argument.
|
|
|
|
docs: String,
|
|
|
|
|
|
|
|
/// If this argument is optional, it should still be included in completion snippets.
|
|
|
|
/// Does not do anything if the argument is already required.
|
|
|
|
#[serde(default)]
|
|
|
|
include_in_snippet: bool,
|
|
|
|
}
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct StdlibMetadata {
|
|
|
|
/// The name of the function in the API.
|
|
|
|
name: String,
|
2024-12-05 14:27:51 -06:00
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
/// Tags for the function.
|
|
|
|
#[serde(default)]
|
|
|
|
tags: Vec<String>,
|
2024-12-05 14:27:51 -06:00
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
/// Whether the function is unpublished.
|
|
|
|
/// Then docs will not be generated.
|
|
|
|
#[serde(default)]
|
|
|
|
unpublished: bool,
|
2024-12-05 14:27:51 -06:00
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
/// Whether the function is deprecated.
|
|
|
|
/// Then specific docs detailing that this is deprecated will be generated.
|
|
|
|
#[serde(default)]
|
|
|
|
deprecated: bool,
|
2024-12-05 14:27:51 -06:00
|
|
|
|
2024-12-16 13:10:31 -05:00
|
|
|
/// Whether the function is displayed in the feature tree.
|
|
|
|
/// If true, calls to the function will be available for display.
|
|
|
|
/// If false, calls to the function will never be displayed.
|
|
|
|
#[serde(default)]
|
|
|
|
feature_tree_operation: bool,
|
|
|
|
|
2024-12-05 14:27:51 -06:00
|
|
|
/// If true, expects keyword arguments.
|
|
|
|
/// If false, expects positional arguments.
|
|
|
|
#[serde(default)]
|
|
|
|
keywords: bool,
|
|
|
|
|
|
|
|
/// If true, the first argument is unlabeled.
|
|
|
|
/// If false, all arguments require labels.
|
|
|
|
#[serde(default)]
|
|
|
|
unlabeled_first: bool,
|
2024-12-13 13:07:52 -06:00
|
|
|
|
|
|
|
/// Key = argument name, value = argument doc.
|
|
|
|
#[serde(default)]
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
args: HashMap<String, ArgMetadata>,
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn do_stdlib(
|
|
|
|
attr: proc_macro2::TokenStream,
|
|
|
|
item: proc_macro2::TokenStream,
|
|
|
|
) -> Result<(proc_macro2::TokenStream, Vec<Error>), Error> {
|
|
|
|
let metadata = from_tokenstream(&attr)?;
|
|
|
|
do_stdlib_inner(metadata, attr, item)
|
|
|
|
}
|
|
|
|
|
2025-04-24 22:01:27 +12:00
|
|
|
fn do_for_each_std_mod(item: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
|
|
|
|
let item: syn::ItemFn = syn::parse2(item.clone()).unwrap();
|
|
|
|
let mut result = proc_macro2::TokenStream::new();
|
|
|
|
for name in fs::read_dir("kcl-lib/std").unwrap().filter_map(|e| {
|
|
|
|
let e = e.unwrap();
|
|
|
|
let filename = e.file_name();
|
|
|
|
filename.to_str().unwrap().strip_suffix(".kcl").map(str::to_owned)
|
|
|
|
}) {
|
|
|
|
let mut item = item.clone();
|
|
|
|
item.sig.ident = syn::Ident::new(&format!("{}_{}", item.sig.ident, name), Span::call_site());
|
|
|
|
let stmts = &item.block.stmts;
|
|
|
|
//let name = format!("\"{name}\"");
|
|
|
|
let block = quote! {
|
|
|
|
{
|
|
|
|
const STD_MOD_NAME: &str = #name;
|
|
|
|
#(#stmts)*
|
|
|
|
}
|
|
|
|
};
|
|
|
|
item.block = Box::new(syn::parse2(block).unwrap());
|
|
|
|
result.extend(Some(item.into_token_stream()));
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2023-08-29 14:12:48 -07:00
|
|
|
fn do_output(res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>) -> proc_macro::TokenStream {
|
2023-08-25 13:41:04 -07:00
|
|
|
match res {
|
|
|
|
Err(err) => err.to_compile_error().into(),
|
|
|
|
Ok((stdlib_docs, errors)) => {
|
|
|
|
let compiler_errors = errors.iter().map(|err| err.to_compile_error());
|
|
|
|
|
|
|
|
let output = quote! {
|
|
|
|
#stdlib_docs
|
|
|
|
#( #compiler_errors )*
|
|
|
|
};
|
|
|
|
|
|
|
|
output.into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn do_stdlib_inner(
|
|
|
|
metadata: StdlibMetadata,
|
|
|
|
_attr: proc_macro2::TokenStream,
|
|
|
|
item: proc_macro2::TokenStream,
|
|
|
|
) -> Result<(proc_macro2::TokenStream, Vec<Error>), Error> {
|
|
|
|
let ast: ItemFnForSignature = syn::parse2(item.clone())?;
|
|
|
|
|
|
|
|
let mut errors = Vec::new();
|
|
|
|
|
|
|
|
if ast.sig.constness.is_some() {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&ast.sig.constness,
|
|
|
|
"stdlib functions may not be const functions",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ast.sig.unsafety.is_some() {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&ast.sig.unsafety,
|
|
|
|
"stdlib functions may not be unsafe",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ast.sig.abi.is_some() {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&ast.sig.abi,
|
|
|
|
"stdlib functions may not use an alternate ABI",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ast.sig.generics.params.is_empty() {
|
2024-06-25 16:10:17 -05:00
|
|
|
if ast.sig.generics.params.iter().any(|generic_type| match generic_type {
|
|
|
|
syn::GenericParam::Lifetime(_) => false,
|
|
|
|
syn::GenericParam::Type(_) => true,
|
|
|
|
syn::GenericParam::Const(_) => true,
|
|
|
|
}) {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&ast.sig.generics,
|
|
|
|
"Stdlib functions may not be generic over types or constants, only lifetimes.",
|
|
|
|
));
|
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ast.sig.variadic.is_some() {
|
|
|
|
errors.push(Error::new_spanned(&ast.sig.variadic, "no language C here"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let name = metadata.name;
|
2024-03-13 12:56:46 -07:00
|
|
|
|
|
|
|
// Fail if the name is not camel case.
|
2024-11-22 19:43:09 -06:00
|
|
|
// Remove some known suffix exceptions first.
|
|
|
|
let name_cleaned = name.strip_suffix("2d").unwrap_or(name.as_str());
|
|
|
|
let name_cleaned = name.strip_suffix("3d").unwrap_or(name_cleaned);
|
|
|
|
if !name_cleaned.is_camel_case() {
|
2024-03-13 12:56:46 -07:00
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&ast.sig.ident,
|
|
|
|
format!("stdlib function names must be in camel case: `{}`", name),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
let name_ident = format_ident!("{}", name.to_case(convert_case::Case::UpperCamel));
|
|
|
|
let name_str = name.to_string();
|
|
|
|
|
|
|
|
let fn_name = &ast.sig.ident;
|
|
|
|
let fn_name_str = fn_name.to_string().replace("inner_", "");
|
|
|
|
let fn_name_ident = format_ident!("{}", fn_name_str);
|
2023-09-20 18:27:08 -07:00
|
|
|
let boxed_fn_name_ident = format_ident!("boxed_{}", fn_name_str);
|
2023-08-25 13:41:04 -07:00
|
|
|
let _visibility = &ast.vis;
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
let doc_info = extract_doc_from_attrs(&ast.attrs);
|
2023-08-25 13:41:04 -07:00
|
|
|
let comment_text = {
|
|
|
|
let mut buf = String::new();
|
|
|
|
buf.push_str("Std lib function: ");
|
|
|
|
buf.push_str(&name_str);
|
2024-03-13 12:56:46 -07:00
|
|
|
if let Some(s) = &doc_info.summary {
|
2023-08-25 13:41:04 -07:00
|
|
|
buf.push_str("\n");
|
|
|
|
buf.push_str(&s);
|
|
|
|
}
|
2024-03-13 12:56:46 -07:00
|
|
|
if let Some(s) = &doc_info.description {
|
2023-08-25 13:41:04 -07:00
|
|
|
buf.push_str("\n");
|
|
|
|
buf.push_str(&s);
|
|
|
|
}
|
|
|
|
buf
|
|
|
|
};
|
|
|
|
let description_doc_comment = quote! {
|
|
|
|
#[doc = #comment_text]
|
|
|
|
};
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
let summary = if let Some(summary) = doc_info.summary {
|
2023-08-25 13:41:04 -07:00
|
|
|
quote! { #summary }
|
|
|
|
} else {
|
|
|
|
quote! { "" }
|
|
|
|
};
|
2024-03-13 12:56:46 -07:00
|
|
|
let description = if let Some(description) = doc_info.description {
|
2023-08-25 13:41:04 -07:00
|
|
|
quote! { #description }
|
|
|
|
} else {
|
|
|
|
quote! { "" }
|
|
|
|
};
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
let cb = doc_info.code_blocks.clone();
|
|
|
|
let code_blocks = if !cb.is_empty() {
|
|
|
|
quote! {
|
|
|
|
let code_blocks = vec![#(#cb),*];
|
|
|
|
code_blocks.iter().map(|cb| {
|
2024-12-06 13:57:31 +13:00
|
|
|
let program = crate::Program::parse_no_errs(cb).unwrap();
|
2024-03-13 12:56:46 -07:00
|
|
|
|
2024-12-05 17:56:49 +13:00
|
|
|
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
2024-03-13 12:56:46 -07:00
|
|
|
options.insert_final_newline = false;
|
2024-11-20 15:19:25 +13:00
|
|
|
program.ast.recast(&options, 0)
|
2024-03-13 12:56:46 -07:00
|
|
|
}).collect::<Vec<String>>()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&ast.sig,
|
|
|
|
"stdlib functions must have at least one code block",
|
|
|
|
));
|
|
|
|
|
|
|
|
quote! { vec![] }
|
|
|
|
};
|
|
|
|
|
|
|
|
// Make sure the function name is in all the code blocks.
|
|
|
|
for code_block in doc_info.code_blocks.iter() {
|
|
|
|
if !code_block.contains(&name) {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&ast.sig,
|
|
|
|
format!(
|
|
|
|
"stdlib functions must have the function name `{}` in the code block",
|
|
|
|
name
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let test_code_blocks = doc_info
|
|
|
|
.code_blocks
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
2024-05-19 17:02:25 -07:00
|
|
|
.map(|(index, code_block)| generate_code_block_test(&fn_name_str, code_block, index))
|
2024-03-13 12:56:46 -07:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
let tags = metadata
|
|
|
|
.tags
|
|
|
|
.iter()
|
|
|
|
.map(|tag| {
|
|
|
|
quote! { #tag.to_string() }
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let deprecated = if metadata.deprecated {
|
|
|
|
quote! { true }
|
|
|
|
} else {
|
|
|
|
quote! { false }
|
|
|
|
};
|
|
|
|
|
|
|
|
let unpublished = if metadata.unpublished {
|
|
|
|
quote! { true }
|
|
|
|
} else {
|
|
|
|
quote! { false }
|
|
|
|
};
|
|
|
|
|
2024-12-16 13:10:31 -05:00
|
|
|
let feature_tree_operation = if metadata.feature_tree_operation {
|
|
|
|
quote! { true }
|
|
|
|
} else {
|
|
|
|
quote! { false }
|
|
|
|
};
|
|
|
|
|
2024-12-05 14:27:51 -06:00
|
|
|
let uses_keyword_arguments = if metadata.keywords {
|
|
|
|
quote! { true }
|
|
|
|
} else {
|
|
|
|
quote! { false }
|
|
|
|
};
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
let docs_crate = get_crate(None);
|
|
|
|
|
|
|
|
// When the user attaches this proc macro to a function with the wrong type
|
|
|
|
// signature, the resulting errors can be deeply inscrutable. To attempt to
|
|
|
|
// make failures easier to understand, we inject code that asserts the types
|
|
|
|
// of the various parameters. We do this by calling dummy functions that
|
|
|
|
// require a type that satisfies SharedExtractor or ExclusiveExtractor.
|
|
|
|
let mut arg_types = Vec::new();
|
2024-12-05 14:27:51 -06:00
|
|
|
for (i, arg) in ast.sig.inputs.iter().enumerate() {
|
2023-08-25 13:41:04 -07:00
|
|
|
// Get the name of the argument.
|
|
|
|
let arg_name = match arg {
|
|
|
|
syn::FnArg::Receiver(pat) => {
|
|
|
|
let span = pat.self_token.span.unwrap();
|
|
|
|
span.source_text().unwrap().to_string()
|
|
|
|
}
|
|
|
|
syn::FnArg::Typed(pat) => match &*pat.pat {
|
|
|
|
syn::Pat::Ident(ident) => ident.ident.to_string(),
|
|
|
|
_ => {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&pat.pat,
|
|
|
|
"stdlib functions may not use destructuring patterns",
|
|
|
|
));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
},
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
.trim_start_matches('_')
|
|
|
|
.to_string();
|
2023-08-25 13:41:04 -07:00
|
|
|
|
|
|
|
let ty = match arg {
|
|
|
|
syn::FnArg::Receiver(pat) => pat.ty.as_ref().into_token_stream(),
|
|
|
|
syn::FnArg::Typed(pat) => pat.ty.as_ref().into_token_stream(),
|
|
|
|
};
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
let (ty_string, ty_ident) = clean_ty_string(ty.to_string().as_str());
|
2023-08-25 13:41:04 -07:00
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
let ty_string = rust_type_to_openapi_type(&ty_string);
|
2024-03-07 12:35:56 -08:00
|
|
|
let required = !ty_ident.to_string().starts_with("Option <");
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
let arg_meta = metadata.args.get(&arg_name);
|
|
|
|
let description = if let Some(s) = arg_meta.map(|arg| &arg.docs) {
|
2024-12-13 13:07:52 -06:00
|
|
|
quote! { #s }
|
|
|
|
} else if metadata.keywords && ty_string != "Args" && ty_string != "ExecState" {
|
|
|
|
errors.push(Error::new_spanned(
|
|
|
|
&arg,
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
"Argument was not documented in the args block",
|
2024-12-13 13:07:52 -06:00
|
|
|
));
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
quote! { String::new() }
|
|
|
|
};
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
let include_in_snippet = required || arg_meta.map(|arg| arg.include_in_snippet).unwrap_or_default();
|
2024-12-05 14:27:51 -06:00
|
|
|
let label_required = !(i == 0 && metadata.unlabeled_first);
|
2025-02-25 10:29:59 -06:00
|
|
|
let camel_case_arg_name = to_camel_case(&arg_name);
|
2024-09-16 15:10:33 -04:00
|
|
|
if ty_string != "ExecState" && ty_string != "Args" {
|
2024-09-30 12:30:22 -07:00
|
|
|
let schema = quote! {
|
2025-03-31 18:02:48 -07:00
|
|
|
generator.root_schema_for::<#ty_ident>()
|
2023-08-25 13:41:04 -07:00
|
|
|
};
|
|
|
|
arg_types.push(quote! {
|
|
|
|
#docs_crate::StdLibFnArg {
|
2025-02-25 10:29:59 -06:00
|
|
|
name: #camel_case_arg_name.to_string(),
|
2023-08-25 13:41:04 -07:00
|
|
|
type_: #ty_string.to_string(),
|
|
|
|
schema: #schema,
|
2024-03-07 12:35:56 -08:00
|
|
|
required: #required,
|
2024-12-05 14:27:51 -06:00
|
|
|
label_required: #label_required,
|
2024-12-13 13:07:52 -06:00
|
|
|
description: #description.to_string(),
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
include_in_snippet: #include_in_snippet,
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-12 12:54:45 -07:00
|
|
|
let return_type_inner = match &ast.sig.output {
|
|
|
|
syn::ReturnType::Default => quote! { () },
|
|
|
|
syn::ReturnType::Type(_, ty) => {
|
|
|
|
// Get the inside of the result.
|
|
|
|
match &**ty {
|
|
|
|
syn::Type::Path(syn::TypePath { path, .. }) => {
|
|
|
|
let path = &path.segments;
|
|
|
|
if path.len() == 1 {
|
|
|
|
let seg = &path[0];
|
|
|
|
if seg.ident == "Result" {
|
|
|
|
if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
|
|
|
args,
|
|
|
|
..
|
|
|
|
}) = &seg.arguments
|
|
|
|
{
|
|
|
|
if args.len() == 2 || args.len() == 1 {
|
|
|
|
let mut args = args.iter();
|
|
|
|
let ok = args.next().unwrap();
|
|
|
|
if let syn::GenericArgument::Type(ty) = ok {
|
2024-03-13 12:56:46 -07:00
|
|
|
let ty = unbox(ty.clone());
|
2024-03-12 12:54:45 -07:00
|
|
|
quote! { #ty }
|
|
|
|
} else {
|
|
|
|
quote! { () }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! { () }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! { () }
|
|
|
|
}
|
|
|
|
} else {
|
2024-03-13 12:56:46 -07:00
|
|
|
let ty = unbox(*ty.clone());
|
2024-03-12 12:54:45 -07:00
|
|
|
quote! { #ty }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! { () }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
quote! { () }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let ret_ty_string = return_type_inner.to_string().replace(' ', "");
|
|
|
|
let return_type = if !ret_ty_string.is_empty() || ret_ty_string != "()" {
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
let ret_ty_string = rust_type_to_openapi_type(&ret_ty_string);
|
2023-09-05 16:02:27 -07:00
|
|
|
quote! {
|
2025-03-31 18:02:48 -07:00
|
|
|
let schema = generator.root_schema_for::<#return_type_inner>();
|
2023-09-05 16:02:27 -07:00
|
|
|
Some(#docs_crate::StdLibFnArg {
|
|
|
|
name: "".to_string(),
|
|
|
|
type_: #ret_ty_string.to_string(),
|
2024-09-28 11:51:08 -07:00
|
|
|
schema,
|
2023-09-05 16:02:27 -07:00
|
|
|
required: true,
|
2024-12-05 14:27:51 -06:00
|
|
|
label_required: true,
|
2024-12-13 13:07:52 -06:00
|
|
|
description: String::new(),
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
include_in_snippet: true,
|
2023-09-05 16:02:27 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! {
|
|
|
|
None
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// For reasons that are not well understood unused constants that use the
|
|
|
|
// (default) call_site() Span do not trigger the dead_code lint. Because
|
|
|
|
// defining but not using an endpoint is likely a programming error, we
|
|
|
|
// want to be sure to have the compiler flag this. We force this by using
|
|
|
|
// the span from the name of the function to which this macro was applied.
|
|
|
|
let span = ast.sig.ident.span();
|
|
|
|
let const_struct = quote_spanned! {span=>
|
|
|
|
pub(crate) const #name_ident: #name_ident = #name_ident {};
|
|
|
|
};
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
let test_mod_name = format_ident!("test_examples_{}", fn_name_str);
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
// The final TokenStream returned will have a few components that reference
|
|
|
|
// `#name_ident`, the name of the function to which this macro was applied...
|
|
|
|
let stream = quote! {
|
2024-03-13 12:56:46 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod #test_mod_name {
|
|
|
|
#(#test_code_blocks)*
|
|
|
|
}
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
// ... a struct type called `#name_ident` that has no members
|
|
|
|
#[allow(non_camel_case_types, missing_docs)]
|
|
|
|
#description_doc_comment
|
2023-09-05 16:02:27 -07:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars::JsonSchema, ts_rs::TS)]
|
|
|
|
#[ts(export)]
|
2023-08-25 13:41:04 -07:00
|
|
|
pub(crate) struct #name_ident {}
|
|
|
|
// ... a constant of type `#name` whose identifier is also #name_ident
|
|
|
|
#[allow(non_upper_case_globals, missing_docs)]
|
|
|
|
#description_doc_comment
|
|
|
|
#const_struct
|
|
|
|
|
2023-09-20 18:27:08 -07:00
|
|
|
fn #boxed_fn_name_ident(
|
2025-02-13 11:59:57 +13:00
|
|
|
exec_state: &mut crate::execution::ExecState,
|
2023-09-20 18:27:08 -07:00
|
|
|
args: crate::std::Args,
|
|
|
|
) -> std::pin::Pin<
|
2024-12-07 07:16:04 +13:00
|
|
|
Box<dyn std::future::Future<Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>> + Send + '_>,
|
2023-09-20 18:27:08 -07:00
|
|
|
> {
|
2024-09-16 15:10:33 -04:00
|
|
|
Box::pin(#fn_name_ident(exec_state, args))
|
2023-09-20 18:27:08 -07:00
|
|
|
}
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
impl #docs_crate::StdLibFn for #name_ident
|
|
|
|
{
|
|
|
|
fn name(&self) -> String {
|
|
|
|
#name_str.to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn summary(&self) -> String {
|
|
|
|
#summary.to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn description(&self) -> String {
|
|
|
|
#description.to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tags(&self) -> Vec<String> {
|
|
|
|
vec![#(#tags),*]
|
|
|
|
}
|
|
|
|
|
2024-12-05 14:27:51 -06:00
|
|
|
fn keyword_arguments(&self) -> bool {
|
|
|
|
#uses_keyword_arguments
|
|
|
|
}
|
|
|
|
|
2024-09-28 11:51:08 -07:00
|
|
|
fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> {
|
2023-08-25 13:41:04 -07:00
|
|
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
2024-09-28 11:51:08 -07:00
|
|
|
// We set this to false so we can recurse them later.
|
|
|
|
settings.inline_subschemas = inline_subschemas;
|
2023-08-25 13:41:04 -07:00
|
|
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
|
|
|
|
|
|
|
vec![#(#arg_types),*]
|
|
|
|
}
|
|
|
|
|
2024-09-28 11:51:08 -07:00
|
|
|
fn return_value(&self, inline_subschemas: bool) -> Option<#docs_crate::StdLibFnArg> {
|
2023-08-25 13:41:04 -07:00
|
|
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
2024-09-28 11:51:08 -07:00
|
|
|
// We set this to false so we can recurse them later.
|
|
|
|
settings.inline_subschemas = inline_subschemas;
|
2023-08-25 13:41:04 -07:00
|
|
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
|
|
|
|
|
|
|
#return_type
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unpublished(&self) -> bool {
|
|
|
|
#unpublished
|
|
|
|
}
|
|
|
|
|
|
|
|
fn deprecated(&self) -> bool {
|
|
|
|
#deprecated
|
|
|
|
}
|
|
|
|
|
2024-12-16 13:10:31 -05:00
|
|
|
fn feature_tree_operation(&self) -> bool {
|
|
|
|
#feature_tree_operation
|
|
|
|
}
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
fn examples(&self) -> Vec<String> {
|
|
|
|
#code_blocks
|
|
|
|
}
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
fn std_lib_fn(&self) -> crate::std::StdFn {
|
2023-09-20 18:27:08 -07:00
|
|
|
#boxed_fn_name_ident
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
fn clone_box(&self) -> Box<dyn #docs_crate::StdLibFn> {
|
|
|
|
Box::new(self.clone())
|
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#item
|
|
|
|
};
|
|
|
|
|
|
|
|
// Prepend the usage message if any errors were detected.
|
|
|
|
if !errors.is_empty() {
|
|
|
|
errors.insert(0, Error::new_spanned(&ast.sig, ""));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((stream, errors))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
|
|
|
|
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
|
|
|
|
quote!(#(#compile_errors)*)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_crate(var: Option<String>) -> proc_macro2::TokenStream {
|
|
|
|
if let Some(s) = var {
|
|
|
|
if let Ok(ts) = syn::parse_str(s.as_str()) {
|
|
|
|
return ts;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
quote!(crate::docs)
|
|
|
|
}
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct DocInfo {
|
|
|
|
pub summary: Option<String>,
|
|
|
|
pub description: Option<String>,
|
|
|
|
pub code_blocks: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn extract_doc_from_attrs(attrs: &[syn::Attribute]) -> DocInfo {
|
2023-08-25 13:41:04 -07:00
|
|
|
let doc = syn::Ident::new("doc", proc_macro2::Span::call_site());
|
2024-03-13 12:56:46 -07:00
|
|
|
let raw_lines = attrs.iter().flat_map(|attr| {
|
2023-08-25 13:41:04 -07:00
|
|
|
if let syn::Meta::NameValue(nv) = &attr.meta {
|
|
|
|
if nv.path.is_ident(&doc) {
|
|
|
|
if let syn::Expr::Lit(syn::ExprLit {
|
2023-08-29 14:12:48 -07:00
|
|
|
lit: syn::Lit::Str(s), ..
|
2023-08-25 13:41:04 -07:00
|
|
|
}) = &nv.value
|
|
|
|
{
|
|
|
|
return normalize_comment_string(s.value());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Vec::new()
|
|
|
|
});
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
// Parse any code blocks from the doc string.
|
2025-03-04 22:53:31 +13:00
|
|
|
let mut code_blocks: Vec<String> = Vec::new();
|
2024-03-13 12:56:46 -07:00
|
|
|
let mut code_block: Option<String> = None;
|
|
|
|
let mut parsed_lines = Vec::new();
|
|
|
|
for line in raw_lines {
|
|
|
|
if line.starts_with("```") {
|
|
|
|
if let Some(ref inner_code_block) = code_block {
|
|
|
|
code_blocks.push(inner_code_block.trim().to_string());
|
|
|
|
code_block = None;
|
|
|
|
} else {
|
|
|
|
code_block = Some(String::new());
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if let Some(ref mut code_block) = code_block {
|
|
|
|
code_block.push_str(&line);
|
|
|
|
code_block.push('\n');
|
|
|
|
} else {
|
|
|
|
parsed_lines.push(line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-04 22:53:31 +13:00
|
|
|
if let Some(code_block) = code_block {
|
|
|
|
code_blocks.push(code_block.trim().to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut summary = None;
|
|
|
|
let mut description: Option<String> = None;
|
2024-03-13 12:56:46 -07:00
|
|
|
for line in parsed_lines {
|
2025-03-04 22:53:31 +13:00
|
|
|
if line.is_empty() {
|
|
|
|
if let Some(desc) = &mut description {
|
|
|
|
// Handle fully blank comments as newlines we keep.
|
|
|
|
if !desc.is_empty() && !desc.ends_with('\n') {
|
|
|
|
if desc.ends_with(' ') {
|
|
|
|
desc.pop().unwrap();
|
|
|
|
}
|
|
|
|
desc.push_str("\n\n");
|
|
|
|
}
|
|
|
|
} else if summary.is_some() {
|
|
|
|
description = Some(String::new());
|
2024-03-13 12:56:46 -07:00
|
|
|
}
|
2025-03-04 22:53:31 +13:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(desc) = &mut description {
|
|
|
|
desc.push_str(&line);
|
|
|
|
// Default to space-separating comment fragments.
|
|
|
|
desc.push(' ');
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if summary.is_none() {
|
|
|
|
summary = Some(String::new());
|
|
|
|
}
|
|
|
|
match &mut summary {
|
|
|
|
Some(summary) => {
|
|
|
|
summary.push_str(&line);
|
|
|
|
// Default to space-separating comment fragments.
|
|
|
|
summary.push(' ');
|
2024-03-13 12:56:46 -07:00
|
|
|
}
|
2025-03-04 22:53:31 +13:00
|
|
|
None => unreachable!(),
|
2024-03-13 12:56:46 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-04 22:53:31 +13:00
|
|
|
// Trim the summary and description.
|
|
|
|
if let Some(s) = &mut summary {
|
|
|
|
while s.ends_with(' ') || s.ends_with('\n') {
|
|
|
|
s.pop().unwrap();
|
|
|
|
}
|
2024-03-13 12:56:46 -07:00
|
|
|
|
2025-03-04 22:53:31 +13:00
|
|
|
if s.is_empty() {
|
|
|
|
summary = None;
|
|
|
|
}
|
2024-03-13 12:56:46 -07:00
|
|
|
}
|
|
|
|
|
2025-03-04 22:53:31 +13:00
|
|
|
if let Some(d) = &mut description {
|
|
|
|
while d.ends_with(' ') || d.ends_with('\n') {
|
|
|
|
d.pop().unwrap();
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2025-03-04 22:53:31 +13:00
|
|
|
if d.is_empty() {
|
|
|
|
description = None;
|
|
|
|
}
|
|
|
|
}
|
2024-03-13 12:56:46 -07:00
|
|
|
|
|
|
|
DocInfo {
|
|
|
|
summary,
|
|
|
|
description,
|
|
|
|
code_blocks,
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn normalize_comment_string(s: String) -> Vec<String> {
|
|
|
|
s.split('\n')
|
2025-03-04 22:53:31 +13:00
|
|
|
.map(|s| {
|
|
|
|
// Rust-style comments are intrinsically single-line.
|
2024-03-13 12:56:46 -07:00
|
|
|
// We only want to trim a single space character from the start of
|
|
|
|
// a line, and only if it's the first character.
|
2025-03-04 22:53:31 +13:00
|
|
|
s.strip_prefix(' ').unwrap_or(s).trim_end().to_owned()
|
2023-08-25 13:41:04 -07:00
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represent an item without concern for its body which may (or may not)
|
|
|
|
/// contain syntax errors.
|
2025-04-24 22:01:27 +12:00
|
|
|
#[derive(Clone)]
|
2023-08-25 13:41:04 -07:00
|
|
|
struct ItemFnForSignature {
|
|
|
|
pub attrs: Vec<Attribute>,
|
|
|
|
pub vis: Visibility,
|
|
|
|
pub sig: Signature,
|
|
|
|
pub _block: proc_macro2::TokenStream,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parse for ItemFnForSignature {
|
|
|
|
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
|
|
|
|
let attrs = input.call(Attribute::parse_outer)?;
|
|
|
|
let vis: Visibility = input.parse()?;
|
|
|
|
let sig: Signature = input.parse()?;
|
|
|
|
let block = input.parse()?;
|
|
|
|
Ok(ItemFnForSignature {
|
|
|
|
attrs,
|
|
|
|
vis,
|
|
|
|
sig,
|
|
|
|
_block: block,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
fn clean_ty_string(t: &str) -> (String, proc_macro2::TokenStream) {
|
2024-06-25 16:10:17 -05:00
|
|
|
let mut ty_string = t
|
|
|
|
.replace("& 'a", "")
|
|
|
|
.replace('&', "")
|
|
|
|
.replace("mut", "")
|
|
|
|
.replace("< 'a >", "")
|
|
|
|
.replace(' ', "");
|
2024-09-16 15:10:33 -04:00
|
|
|
if ty_string.starts_with("ExecState") {
|
|
|
|
ty_string = "ExecState".to_string();
|
|
|
|
}
|
2024-02-12 12:18:37 -08:00
|
|
|
if ty_string.starts_with("Args") {
|
|
|
|
ty_string = "Args".to_string();
|
|
|
|
}
|
|
|
|
let ty_string = ty_string.trim().to_string();
|
|
|
|
let ty_ident = if ty_string.starts_with("Vec<") {
|
|
|
|
let ty_string = ty_string.trim_start_matches("Vec<").trim_end_matches('>');
|
|
|
|
let (_, ty_ident) = clean_ty_string(&ty_string);
|
|
|
|
quote! {
|
|
|
|
Vec<#ty_ident>
|
|
|
|
}
|
|
|
|
} else if ty_string.starts_with("kittycad::types::") {
|
|
|
|
let ty_string = ty_string.trim_start_matches("kittycad::types::").trim_end_matches('>');
|
|
|
|
let ty_ident = format_ident!("{}", ty_string);
|
|
|
|
quote! {
|
|
|
|
kittycad::types::#ty_ident
|
|
|
|
}
|
|
|
|
} else if ty_string.starts_with("Option<") {
|
|
|
|
let ty_string = ty_string.trim_start_matches("Option<").trim_end_matches('>');
|
|
|
|
let (_, ty_ident) = clean_ty_string(&ty_string);
|
|
|
|
quote! {
|
|
|
|
Option<#ty_ident>
|
|
|
|
}
|
|
|
|
} else if let Some((inner_array_type, num)) = parse_array_type(&ty_string) {
|
|
|
|
let ty_string = inner_array_type.to_owned();
|
|
|
|
let (_, ty_ident) = clean_ty_string(&ty_string);
|
|
|
|
quote! {
|
|
|
|
[#ty_ident; #num]
|
|
|
|
}
|
|
|
|
} else if ty_string.starts_with("Box<") {
|
|
|
|
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
|
|
|
|
let (_, ty_ident) = clean_ty_string(&ty_string);
|
|
|
|
quote! {
|
|
|
|
#ty_ident
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let ty_ident = format_ident!("{}", ty_string);
|
|
|
|
quote! {
|
|
|
|
#ty_ident
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
(ty_string, ty_ident)
|
|
|
|
}
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
fn rust_type_to_openapi_type(t: &str) -> String {
|
2023-08-25 13:41:04 -07:00
|
|
|
let mut t = t.to_string();
|
|
|
|
// Turn vecs into arrays.
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
// TODO: handle nested types
|
2023-08-25 13:41:04 -07:00
|
|
|
if t.starts_with("Vec<") {
|
|
|
|
t = t.replace("Vec<", "[").replace('>', "]");
|
|
|
|
}
|
2023-09-19 14:20:14 -07:00
|
|
|
if t.starts_with("Box<") {
|
|
|
|
t = t.replace("Box<", "").replace('>', "");
|
|
|
|
}
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
if t.starts_with("Option<") {
|
|
|
|
t = t.replace("Option<", "").replace('>', "");
|
|
|
|
}
|
|
|
|
if let Some((inner_type, _length)) = parse_array_type(&t) {
|
|
|
|
t = format!("[{inner_type}]")
|
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
|
2025-04-07 19:02:41 +12:00
|
|
|
if t == "f64" || t == "TyF64" {
|
2023-08-25 13:41:04 -07:00
|
|
|
return "number".to_string();
|
2024-12-17 09:38:32 +13:00
|
|
|
} else if t == "u32" {
|
|
|
|
return "integer".to_string();
|
2023-08-25 13:41:04 -07:00
|
|
|
} else if t == "str" {
|
|
|
|
return "string".to_string();
|
|
|
|
} else {
|
2025-04-07 19:02:41 +12:00
|
|
|
return t.replace("f64", "number").replace("TyF64", "number").to_string();
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
fn parse_array_type(type_name: &str) -> Option<(&str, usize)> {
|
|
|
|
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[([a-zA-Z0-9<>]+); ?(\d+)\]").unwrap());
|
|
|
|
let cap = RE.captures(type_name)?;
|
|
|
|
let inner_type = cap.get(1)?;
|
|
|
|
let length = cap.get(2)?.as_str().parse().ok()?;
|
|
|
|
Some((inner_type.as_str(), length))
|
|
|
|
}
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
// For each kcl code block, we want to generate a test that checks that the
|
|
|
|
// code block is valid kcl code and compiles and executes.
|
2024-05-19 17:02:25 -07:00
|
|
|
fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> proc_macro2::TokenStream {
|
2024-08-12 13:06:30 -07:00
|
|
|
let test_name = format_ident!("kcl_test_example_{}{}", fn_name, index);
|
2024-05-15 10:17:29 -07:00
|
|
|
let test_name_mock = format_ident!("test_mock_example_{}{}", fn_name, index);
|
2024-08-12 13:06:30 -07:00
|
|
|
let output_test_name_str = format!("serial_test_example_{}{}", fn_name, index);
|
2024-03-12 12:54:45 -07:00
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
quote! {
|
2024-05-15 10:17:29 -07:00
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
2024-12-18 08:52:17 -06:00
|
|
|
async fn #test_name_mock() -> miette::Result<()> {
|
2024-12-06 13:57:31 +13:00
|
|
|
let program = crate::Program::parse_no_errs(#code_block).unwrap();
|
2024-12-07 07:16:04 +13:00
|
|
|
let ctx = crate::ExecutorContext {
|
2024-05-15 10:17:29 -07:00
|
|
|
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
|
|
|
|
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
|
|
|
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
|
|
|
settings: Default::default(),
|
2024-12-07 07:16:04 +13:00
|
|
|
context_type: crate::execution::ContextType::Mock,
|
2024-05-15 10:17:29 -07:00
|
|
|
};
|
|
|
|
|
2025-03-15 10:08:39 -07:00
|
|
|
if let Err(e) = ctx.run(&program, &mut crate::execution::ExecState::new(&ctx)).await {
|
2024-12-18 08:52:17 -06:00
|
|
|
return Err(miette::Report::new(crate::errors::Report {
|
2025-03-07 18:45:33 -08:00
|
|
|
error: e.error,
|
2024-12-18 08:52:17 -06:00
|
|
|
filename: format!("{}{}", #fn_name, #index),
|
|
|
|
kcl_source: #code_block.to_string(),
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
Ok(())
|
2024-05-15 10:17:29 -07:00
|
|
|
}
|
|
|
|
|
2024-03-13 12:56:46 -07:00
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
2024-12-18 08:52:17 -06:00
|
|
|
async fn #test_name() -> miette::Result<()> {
|
2024-07-31 09:54:46 -05:00
|
|
|
let code = #code_block;
|
|
|
|
// Note, `crate` must be kcl_lib
|
2025-03-31 10:56:03 -04:00
|
|
|
let result = match crate::test_server::execute_and_snapshot(code, None).await {
|
2024-12-18 08:52:17 -06:00
|
|
|
Err(crate::errors::ExecError::Kcl(e)) => {
|
|
|
|
return Err(miette::Report::new(crate::errors::Report {
|
2025-01-08 20:02:30 -05:00
|
|
|
error: e.error,
|
2024-12-18 08:52:17 -06:00
|
|
|
filename: format!("{}{}", #fn_name, #index),
|
|
|
|
kcl_source: #code_block.to_string(),
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
Err(other_err)=> panic!("{}", other_err),
|
|
|
|
Ok(img) => img,
|
|
|
|
};
|
2024-08-12 13:06:30 -07:00
|
|
|
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #output_test_name_str), &result, 0.99);
|
2024-12-18 08:52:17 -06:00
|
|
|
Ok(())
|
2024-03-13 12:56:46 -07:00
|
|
|
}
|
2024-03-12 12:54:45 -07:00
|
|
|
}
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|