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>
This commit is contained in:
Nick Cameron
2025-05-13 14:59:23 +12:00
committed by GitHub
parent 47feae3bd9
commit 01c7b69f50
16 changed files with 209 additions and 47 deletions

View File

@ -10,7 +10,7 @@ Apply a function to every element of a list.
```kcl ```kcl
map( map(
@array: [any], @array: [any],
f: Fn, f: fn(any): any,
): [any] ): [any]
``` ```
@ -22,7 +22,7 @@ Given a list like `[a, b, c]`, and a function like `f`, returns
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `array` | [`[any]`](/docs/kcl-std/types/std-types-any) | Input array. The output array is this input array, but every element has had the function `f` run on it. | Yes | | `array` | [`[any]`](/docs/kcl-std/types/std-types-any) | Input array. The output array is this input array, but every element has had the function `f` run on it. | Yes |
| `f` | [`Fn`](/docs/kcl-std/types/std-types-Fn) | A function. The output array is just the input array, but `f` has been run on every item. | Yes | | `f` | [`fn(any): any`](/docs/kcl-std/types/std-types-fn) | A function. The output array is just the input array, but `f` has been run on every item. | Yes |
### Returns ### Returns

View File

@ -11,7 +11,7 @@ layout: manual
reduce( reduce(
@array: [any], @array: [any],
initial: any, initial: any,
f: Fn, f: fn(any, accum: any): any,
): [any] ): [any]
``` ```
@ -24,7 +24,7 @@ using the previous value and the element.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `array` | [`[any]`](/docs/kcl-std/types/std-types-any) | Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run. | Yes | | `array` | [`[any]`](/docs/kcl-std/types/std-types-any) | Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run. | Yes |
| `initial` | [`any`](/docs/kcl-std/types/std-types-any) | The first time `f` is run, it will be called with the first item of `array` and this initial starting value. | Yes | | `initial` | [`any`](/docs/kcl-std/types/std-types-any) | The first time `f` is run, it will be called with the first item of `array` and this initial starting value. | Yes |
| `f` | [`Fn`](/docs/kcl-std/types/std-types-Fn) | Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`. | Yes | | `f` | [`fn(any, accum: any): any`](/docs/kcl-std/types/std-types-fn) | Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`. | Yes |
### Returns ### Returns

View File

@ -140,13 +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)
* [`Fn`](/docs/kcl-std/types/std-types-Fn)
* [`ImportedGeometry`](/docs/kcl-std/types/std-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

@ -17,7 +17,6 @@ Types can (optionally) be used to describe a function's arguments and returned v
* [`Axis3d`](/docs/kcl-std/types/std-types-Axis3d) * [`Axis3d`](/docs/kcl-std/types/std-types-Axis3d)
* [`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)
* [`Fn`](/docs/kcl-std/types/std-types-Fn)
* [`Helix`](/docs/kcl-std/types/std-types-Helix) * [`Helix`](/docs/kcl-std/types/std-types-Helix)
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) * [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
* [`Plane`](/docs/kcl-std/types/std-types-Plane) * [`Plane`](/docs/kcl-std/types/std-types-Plane)
@ -27,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

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

View File

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

@ -39,7 +39,7 @@ const DECLARED_TYPES: [&str; 17] = [
"Axis2d", "Axis2d",
"Axis3d", "Axis3d",
"ImportedGeometry", "ImportedGeometry",
"Fn", "fn",
]; ];
lazy_static::lazy_static! { lazy_static::lazy_static! {

View File

@ -183,7 +183,7 @@ impl RuntimeType {
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::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
AstPrimitiveType::Function => RuntimeType::Primitive(PrimitiveType::Function), AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
}) })
} }

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,
@ -233,13 +233,28 @@ impl PrimitiveType {
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::ImportedGeometry => hasher.update(b"ImportedGeometry"),
PrimitiveType::Function => hasher.update(b"Fn"), 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

@ -3199,8 +3199,8 @@ pub enum PrimitiveType {
Tag, Tag,
/// Imported from other CAD system. /// Imported from other CAD system.
ImportedGeometry, ImportedGeometry,
/// `Fn`, type of functions. /// `fn`, type of functions.
Function, Function(FunctionType),
/// An identifier used as a type (not really a primitive type, but whatever). /// An identifier used as a type (not really a primitive type, but whatever).
Named(Node<Identifier>), Named(Node<Identifier>),
} }
@ -3215,7 +3215,6 @@ impl PrimitiveType {
("number", None) => Some(PrimitiveType::Number(NumericSuffix::None)), ("number", None) => Some(PrimitiveType::Number(NumericSuffix::None)),
("number", Some(s)) => Some(PrimitiveType::Number(s)), ("number", Some(s)) => Some(PrimitiveType::Number(s)),
("ImportedGeometry", None) => Some(PrimitiveType::ImportedGeometry), ("ImportedGeometry", None) => Some(PrimitiveType::ImportedGeometry),
("Fn", None) => Some(PrimitiveType::Function),
_ => None, _ => None,
} }
} }
@ -3236,12 +3235,57 @@ impl fmt::Display for PrimitiveType {
PrimitiveType::Boolean => write!(f, "bool"), PrimitiveType::Boolean => write!(f, "bool"),
PrimitiveType::Tag => write!(f, "tag"), PrimitiveType::Tag => write!(f, "tag"),
PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"), PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
PrimitiveType::Function => write!(f, "Fn"), PrimitiveType::Function(t) => {
write!(f, "fn")?;
if t.unnamed_arg.is_some() || !t.named_args.is_empty() || t.return_type.is_some() {
write!(f, "(")?;
if let Some(u) = &t.unnamed_arg {
write!(f, "{u}")?;
if !t.named_args.is_empty() {
write!(f, ", ")?;
}
}
for (i, (a, t)) in t.named_args.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
write!(f, "{}: {t}", a.name)?;
}
write!(f, ")")?;
if let Some(r) = &t.return_type {
write!(f, ": {r}")?;
}
}
Ok(())
}
PrimitiveType::Named(n) => write!(f, "{}", n.name), PrimitiveType::Named(n) => write!(f, "{}", n.name),
} }
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct FunctionType {
pub unnamed_arg: Option<BoxNode<Type>>,
pub named_args: Vec<(Node<Identifier>, Node<Type>)>,
pub return_type: Option<BoxNode<Type>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub digest: Option<Digest>,
}
impl FunctionType {
pub fn empty_fn_type() -> Self {
FunctionType {
unnamed_arg: None,
named_args: Vec::new(),
return_type: None,
digest: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -3293,7 +3337,7 @@ impl fmt::Display for Type {
} else { } else {
write!(f, ",")?; write!(f, ",")?;
} }
write!(f, "{}: ", p.identifier.name)?; write!(f, " {}:", p.identifier.name)?;
if let Some(ty) = &p.type_ { if let Some(ty) = &p.type_ {
write!(f, " {}", ty.inner)?; write!(f, " {}", ty.inner)?;
} }

View File

@ -25,11 +25,11 @@ use crate::{
ast::types::{ ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement, BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
FunctionExpression, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement,
LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeList, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name,
NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter,
PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type, TypeDeclaration, PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type,
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind, TypeDeclaration, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
}, },
math::BinaryExpressionToken, math::BinaryExpressionToken,
token::{Token, TokenSlice, TokenType}, token::{Token, TokenSlice, TokenType},
@ -1261,7 +1261,7 @@ fn function_decl(i: &mut TokenSlice) -> PResult<Node<FunctionExpression>> {
fn return_type(i: &mut TokenSlice) -> PResult<Node<Type>> { fn return_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
colon(i)?; colon(i)?;
ignore_whitespace(i); ignore_whitespace(i);
argument_type(i) type_(i)
} }
let open = open_paren(i)?; let open = open_paren(i)?;
@ -2013,7 +2013,7 @@ fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> {
.context(expected("a KCL value")) .context(expected("a KCL value"))
.parse_next(i)?; .parse_next(i)?;
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?; let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
if let Some((_, _, ty)) = ty { if let Some((_, _, ty)) = ty {
expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty))) expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
} }
@ -2083,7 +2083,7 @@ fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
)) ))
.parse_next(i)?; .parse_next(i)?;
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?; let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
if let Some((_, _, ty)) = ty { if let Some((_, _, ty)) = ty {
expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty))) expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
} }
@ -2233,7 +2233,21 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start); let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start);
whitespace(i)?; whitespace(i)?;
let name = identifier(i)?; let name = alt((
fun.map(|t| {
Node::new(
Identifier {
name: "fn".to_owned(),
digest: None,
},
t.start,
t.end,
t.module_id,
)
}),
identifier,
))
.parse_next(i)?;
let mut end = name.end; let mut end = name.end;
let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() { let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() {
@ -2253,7 +2267,7 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
ignore_whitespace(i); ignore_whitespace(i);
equals(i)?; equals(i)?;
ignore_whitespace(i); ignore_whitespace(i);
let ty = argument_type(i)?; let ty = type_(i)?;
ParseContext::warn(CompilationError::err( ParseContext::warn(CompilationError::err(
ty.as_source_range(), ty.as_source_range(),
@ -2755,12 +2769,8 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
.parse_next(i) .parse_next(i)
} }
/// A type of a function argument. /// Parse a type in various positions.
/// This can be: fn type_(i: &mut TokenSlice) -> PResult<Node<Type>> {
/// - a primitive type, e.g. `number` or `string` or `bool`
/// - an array type, e.g. `[number]` or `[string]` or `[bool]`
/// - an object type, e.g. `{x: number, y: number}` or `{name: string, age: number}`
fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
let type_ = alt(( let type_ = alt((
// Object types // Object types
// TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`. // TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`.
@ -2800,14 +2810,69 @@ fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
} }
fn primitive_type(i: &mut TokenSlice) -> PResult<Node<PrimitiveType>> { fn primitive_type(i: &mut TokenSlice) -> PResult<Node<PrimitiveType>> {
let ident = identifier(i)?; alt((
// A function type: `fn` (`(` type?, (id: type,)* `)` (`:` type)?)?
(
fun,
opt((
// `(` type?, (id: type,)* `)`
delimited(
open_paren,
opt(alt((
// type, (id: type,)+
(
type_,
comma,
opt(whitespace),
separated(
1..,
(identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
comma_sep,
),
)
.map(|(t, _, _, args)| (Some(t), args)),
// (id: type,)+
separated(
1..,
(identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
comma_sep,
)
.map(|args| (None, args)),
// type
type_.map(|t| (Some(t), Vec::new())),
))),
close_paren,
),
// `:` type
opt((colon, opt(whitespace), type_)),
)),
)
.map(|(t, tys)| {
let mut ft = FunctionType::empty_fn_type();
let suffix = opt(delimited(open_paren, uom_for_type, close_paren)).parse_next(i)?; if let Some((args, ret)) = tys {
if let Some((unnamed, named)) = args {
if let Some(unnamed) = unnamed {
ft.unnamed_arg = Some(Box::new(unnamed));
}
ft.named_args = named;
}
if let Some((_, _, ty)) = ret {
ft.return_type = Some(Box::new(ty));
}
}
let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id); Node::new(PrimitiveType::Function(ft), t.start, t.end, t.module_id)
result.inner = PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident)); }),
// A named type, possibly with a numeric suffix.
Ok(result) (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
result.inner =
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
result
}),
))
.parse_next(i)
} }
fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> { fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
@ -2817,7 +2882,7 @@ fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
} }
open_bracket(i)?; open_bracket(i)?;
let ty = argument_type(i)?; let ty = type_(i)?;
let len = opt(( let len = opt((
semi_colon, semi_colon,
opt_whitespace, opt_whitespace,
@ -2905,7 +2970,7 @@ fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"), any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
opt(question_mark), opt(question_mark),
opt(whitespace), opt(whitespace),
opt((colon, opt(whitespace), argument_type).map(|tup| tup.2)), opt((colon, opt(whitespace), type_).map(|tup| tup.2)),
opt(whitespace), opt(whitespace),
opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)), opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)),
) )
@ -4777,6 +4842,20 @@ let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
assert_no_err(some_program_string); assert_no_err(some_program_string);
} }
#[test] #[test]
fn parse_function_types() {
let code = r#"foo = x: fn
foo = x: fn(number)
fn foo(x: fn(): number): fn { return 0 }
fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 }
type fn
type foo = fn
type foo = fn(a: string, b: { f: fn(): any })
type foo = fn([fn])
type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
"#;
assert_no_err(code);
}
#[test]
fn test_parse_tag_starting_with_bang() { fn test_parse_tag_starting_with_bang() {
let some_program_string = r#"startSketchOn(XY) let some_program_string = r#"startSketchOn(XY)
|> startProfile(at = [0, 0]) |> startProfile(at = [0, 0])

View File

@ -2575,6 +2575,27 @@ sketch002 = startSketchOn({
assert_eq!(actual, input); assert_eq!(actual, input);
} }
#[test]
fn recast_function_types() {
let input = r#"foo = x: fn
foo = x: fn(number)
fn foo(x: fn(): number): fn {
return 0
}
fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn {
return 0
}
type fn
type foo = fn
type foo = fn(a: string, b: { f: fn(): any })
type foo = fn([fn])
type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
"#;
let ast = crate::parsing::top_level_parse(input).unwrap();
let actual = ast.recast(&FormatOptions::new(), 0);
assert_eq!(actual, input);
}
#[test] #[test]
fn unparse_call_inside_function_args_multiple_lines() { fn unparse_call_inside_function_args_multiple_lines() {
let input = r#"fn foo() { let input = r#"fn foo() {

View File

@ -40,7 +40,7 @@ export fn map(
/// Input array. The output array is this input array, but every element has had the function `f` run on it. /// Input array. The output array is this input array, but every element has had the function `f` run on it.
@array: [any], @array: [any],
/// A function. The output array is just the input array, but `f` has been run on every item. /// A function. The output array is just the input array, but `f` has been run on every item.
f: Fn, f: fn(any): any,
): [any] {} ): [any] {}
/// Take a starting value. Then, for each element of an array, calculate the next value, /// Take a starting value. Then, for each element of an array, calculate the next value,
@ -132,7 +132,7 @@ export fn reduce(
/// The first time `f` is run, it will be called with the first item of `array` and this initial starting value. /// The first time `f` is run, it will be called with the first item of `array` and this initial starting value.
initial: any, initial: any,
/// Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`. /// Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`.
f: Fn, f: fn(any, accum: any): any,
): [any] {} ): [any] {}
/// Append an element to the end of an array. /// Append an element to the end of an array.

View File

@ -169,7 +169,7 @@ export type ImportedGeometry
/// The type of any function in KCL. /// The type of any function in KCL.
@(impl = primitive) @(impl = primitive)
export type Fn export type fn
/// An abstract plane. /// An abstract plane.
/// ///

View File

@ -4,7 +4,8 @@ description: Error from executing argument_error.kcl
--- ---
KCL Semantic error KCL Semantic error
× semantic: f requires a value with type `Fn`, but found array (list) × semantic: f requires a value with type `fn(any): any`, but found array
│ (list)
╭─[5:1] ╭─[5:1]
4 │ 4 │
5 │ map(f, f = [0, 1]) 5 │ map(f, f = [0, 1])
@ -14,7 +15,8 @@ KCL Semantic error
╰──── ╰────
╰─▶ KCL Semantic error ╰─▶ KCL Semantic error
× semantic: f requires a value with type `Fn`, but found array (list) × semantic: f requires a value with type `fn(any): any`, but found
│ array (list)
╭─[5:12] ╭─[5:12]
4 │ 4 │
5 │ map(f, f = [0, 1]) 5 │ map(f, f = [0, 1])

View File

@ -709,7 +709,6 @@ flowchart LR
115 --- 179 115 --- 179
115 x--> 228 115 x--> 228
115 --- 256 115 --- 256
115 x--> 307
115 --- 308 115 --- 308
164 <--x 116 164 <--x 116
116 --- 184 116 --- 184