Declare std kwarg functions in KCL and migrate circle (#5955)

* Support calling KCL std KW fns, and move circle to KCL std

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

* Doc comments on parameters

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

* Update grammar

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

* Change use of counterClockWise to ccw

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-03-24 21:55:24 +13:00
committed by GitHub
parent dddcd5ff46
commit 3b2abe5814
94 changed files with 16657 additions and 9803 deletions

View File

@ -467,6 +467,10 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String)
});
let output = hbs.render("kclType", &data)?;
let output = cleanup_type_links(
&output,
ty.referenced_types.iter().filter(|t| !DECLARED_TYPES.contains(&&***t)),
);
expectorate::assert_contents(format!("../../docs/kcl/{}.md", file_name), &output);
Ok(())
@ -514,6 +518,13 @@ fn generate_function_from_kcl(function: &FnData, file_name: String) -> Result<()
});
let output = hbs.render("function", &data)?;
let output = cleanup_type_links(
&output,
function
.referenced_types
.iter()
.filter(|t| !DECLARED_TYPES.contains(&&***t)),
);
expectorate::assert_contents(format!("../../docs/kcl/{}.md", file_name), &output);
Ok(())
@ -677,6 +688,12 @@ fn cleanup_type_links<'a>(output: &str, types: impl Iterator<Item = &'a String>)
}
}
// TODO handle union types generically rather than special casing them.
cleaned_output = cleaned_output.replace(
"`Sketch | Plane | Face`",
"[`Sketch`](/docs/kcl/types/Sketch) `|` [`Plane`](/docs/kcl/types/Face) `|` [`Plane`](/docs/kcl/types/Face)",
);
cleanup_static_links(&cleaned_output)
}

View File

@ -1,4 +1,4 @@
use std::str::FromStr;
use std::{collections::HashSet, str::FromStr};
use regex::Regex;
use tower_lsp::lsp_types::{
@ -9,7 +9,7 @@ use tower_lsp::lsp_types::{
use crate::{
execution::annotations,
parsing::{
ast::types::{Annotation, Node, NonCodeNode, VariableKind},
ast::types::{Annotation, Node, PrimitiveType, Type, VariableKind},
token::NumericSuffix,
},
ModuleId,
@ -59,7 +59,6 @@ impl CollectionVisitor {
format!("std::{}::", self.name)
};
let mut dd = match var.kind {
// TODO metadata for args
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name)),
VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual_name)),
};
@ -322,6 +321,8 @@ pub struct FnData {
/// Code examples.
/// These are tested and we know they compile and execute.
pub examples: Vec<(String, ExampleProperties)>,
#[allow(dead_code)]
pub referenced_types: Vec<String>,
}
impl FnData {
@ -332,6 +333,17 @@ impl FnData {
};
let name = var.declaration.id.name.clone();
qual_name.push_str(&name);
let mut referenced_types = HashSet::new();
if let Some(t) = &expr.return_type {
collect_type_names(&mut referenced_types, t);
}
for p in &expr.params {
if let Some(t) = &p.type_ {
collect_type_names(&mut referenced_types, t);
}
}
FnData {
name,
qual_name,
@ -346,6 +358,7 @@ impl FnData {
summary: None,
description: None,
examples: Vec::new(),
referenced_types: referenced_types.into_iter().collect(),
}
}
@ -414,7 +427,7 @@ impl FnData {
}
#[allow(clippy::literal_string_with_formatting_args)]
fn to_autocomplete_snippet(&self) -> String {
pub(super) fn to_autocomplete_snippet(&self) -> String {
if self.name == "loft" {
return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned();
} else if self.name == "hole" {
@ -480,12 +493,12 @@ pub struct ArgData {
/// If the argument is required.
pub kind: ArgKind,
/// Additional information that could be used instead of the type's description.
/// This is helpful if the type is really basic, like "u32" -- that won't tell the user much about
/// This is helpful if the type is really basic, like "number" -- that won't tell the user much about
/// how this argument is meant to be used.
pub docs: Option<String>,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ArgKind {
Special,
// Parameter is whether the arg is optional.
@ -495,38 +508,47 @@ pub enum ArgKind {
impl ArgData {
fn from_ast(arg: &crate::parsing::ast::types::Parameter) -> Self {
ArgData {
let mut result = ArgData {
name: arg.identifier.name.clone(),
ty: arg.type_.as_ref().map(|t| t.to_string()),
// Doc comments are not yet supported on parameters.
docs: None,
kind: if arg.labeled {
ArgKind::Labelled(arg.optional())
} else {
ArgKind::Special
},
}
}
};
fn _with_meta(&mut self, _meta: &[Node<NonCodeNode>]) {
// TODO use comments for docs (we can't currently get the comments for an argument)
result.with_comments(&arg.identifier.pre_comments);
result
}
pub fn get_autocomplete_snippet(&self, index: usize) -> Option<(usize, String)> {
match &self.ty {
Some(s)
if [
"Sketch",
"SketchSet",
"Solid",
"SolidSet",
"SketchSurface",
"SketchOrSurface",
]
.contains(&&**s) =>
{
Some((index, format!("${{{}:{}}}", index, "%")))
let label = if self.kind == ArgKind::Special {
String::new()
} else {
format!("{} = ", self.name)
};
match self.ty.as_deref() {
Some(s) if ["Sketch", "Solid", "Plane | Face", "Sketch | Plane | Face"].contains(&s) => {
Some((index, format!("{label}${{{}:{}}}", index, "%")))
}
Some("number") if self.kind.required() => Some((index, format!(r#"{label}${{{}:3.14}}"#, index))),
Some("Point2d") if self.kind.required() => Some((
index + 1,
format!(r#"{label}[${{{}:3.14}}, ${{{}:3.14}}]"#, index, index + 1),
)),
Some("Point3d") if self.kind.required() => Some((
index + 2,
format!(
r#"{label}[${{{}:3.14}}, ${{{}:3.14}}, ${{{}:3.14}}]"#,
index,
index + 1,
index + 2
),
)),
Some("string") if self.kind.required() => Some((index, format!(r#"{label}${{{}:"string"}}"#, index))),
Some("bool") if self.kind.required() => Some((index, format!(r#"{label}${{{}:false}}"#, index))),
_ => None,
}
}
@ -570,12 +592,19 @@ pub struct TyData {
/// Code examples.
/// These are tested and we know they compile and execute.
pub examples: Vec<(String, ExampleProperties)>,
#[allow(dead_code)]
pub referenced_types: Vec<String>,
}
impl TyData {
fn from_ast(ty: &crate::parsing::ast::types::TypeDeclaration, mut qual_name: String) -> Self {
let name = ty.name.name.clone();
qual_name.push_str(&name);
let mut referenced_types = HashSet::new();
if let Some(t) = &ty.alias {
collect_type_names(&mut referenced_types, t);
}
TyData {
name,
qual_name,
@ -589,6 +618,7 @@ impl TyData {
summary: None,
description: None,
examples: Vec::new(),
referenced_types: referenced_types.into_iter().collect(),
}
}
@ -852,6 +882,66 @@ impl ApplyMeta for TyData {
}
}
impl ApplyMeta for ArgData {
fn apply_docs(
&mut self,
summary: Option<String>,
description: Option<String>,
_examples: Vec<(String, ExampleProperties)>,
) {
let Some(mut docs) = summary else {
return;
};
if let Some(desc) = description {
docs.push_str("\n\n");
docs.push_str(&desc);
}
self.docs = Some(docs);
}
fn deprecated(&mut self, _deprecated: bool) {
unreachable!();
}
fn doc_hidden(&mut self, _doc_hidden: bool) {
unreachable!();
}
fn impl_kind(&mut self, _impl_kind: annotations::Impl) {
unreachable!();
}
}
fn collect_type_names(acc: &mut HashSet<String>, ty: &Type) {
match ty {
Type::Primitive(primitive_type) => {
acc.insert(collect_type_names_from_primitive(primitive_type));
}
Type::Array { ty, .. } => {
acc.insert(collect_type_names_from_primitive(ty));
}
Type::Union { tys } => tys.iter().for_each(|t| {
acc.insert(collect_type_names_from_primitive(t));
}),
Type::Object { properties } => properties.iter().for_each(|p| {
if let Some(t) = &p.type_ {
collect_type_names(acc, t)
}
}),
}
}
fn collect_type_names_from_primitive(ty: &PrimitiveType) -> String {
match ty {
PrimitiveType::String => "string".to_owned(),
PrimitiveType::Number(_) => "number".to_owned(),
PrimitiveType::Boolean => "bool".to_owned(),
PrimitiveType::Tag => "tag".to_owned(),
PrimitiveType::Named(id) => id.name.clone(),
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -927,6 +927,8 @@ fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) -> Res
mod tests {
use pretty_assertions::assert_eq;
use crate::docs::kcl_doc::{self, DocData};
use super::StdLibFn;
#[test]
@ -1007,8 +1009,11 @@ mod tests {
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_circle() {
let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle);
let snippet = circle_fn.to_autocomplete_snippet().unwrap();
let data = kcl_doc::walk_prelude();
let DocData::Fn(circle_fn) = data.into_iter().find(|d| d.name() == "circle").unwrap() else {
panic!();
};
let snippet = circle_fn.to_autocomplete_snippet();
assert_eq!(
snippet,
r#"circle(${0:%}, center = [${1:3.14}, ${2:3.14}], radius = ${3:3.14})${}"#