Skip to content

Attributes #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Attributes syntax

We're currently using `@` and some ad hoc following stuff.

Proposed changes:

- Inner vs outer attribute semantics
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of inner and outer, could we call them item attributes and scope attributes, respectively? Seems clearer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like item attr, but I find 'scope' a bit ambiguous. I was hoping to not use either terminology in user-facing docs, just named/unnamed and explain how they apply

- Attachment in AST
- Inner attributes with no identifier
- Tool attributes

## Proposal

Goal: to be flexible and extensible.

### Syntax

Inner/outer is not meant to be user-facing jargon.

```
item ::= ... | fn_decl | const_decl | import | inner_attr | outer_attr item
stmt ::= ... | const_decl | outer_attr stmt

inner_attr ::= `@` `(` (annot_item,)* `)`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lack of identifier after the @ for inner attributes makes it awkward to use many, unrelated attributes for an item. Instead of them looking like function calls, they would need to be differentiated by keyword arguments. An example I'm thinking about would be in Rust #[derive(Clone, Serialize)] and #[serde(rename_all = "camelCase")] on a struct. How would you do the equivalent here?

@(derive = [Clone, Serialize], serde = { rename_all = camelCase })

I know KCL is very different from Rust, so the specifics don't apply. But in most languages that have item attributes, it's common to want to apply multiple, unrelated attributes to the same declaration.

I think I'd prefer a different token or tokens the way Rust does it with # vs. #!.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking like:

@(derive = [Clone, Serialize])
@(serde = { rename_all = camelCase })

So, the logic here is that the name after the @ for inner attrs is showing the target of the attribute, whereas with outer attrs the target is always the following item

outer_attr ::= `@` id (`(` (annot_item,)* `)`)?
annot_item ::= id `=` expr
```

Note: `expr` in `annot_item` is a parsing requirement but not a semantic one, so e.g., `@(defaultLengthUnit = mm)` is ok and `mm` is interpreted as a unit, not an identifier.

Inner attributes cannot be applied to outer attributes, but multiple attributes (inner or outer) can be applied to a single item, attributes combine rather than override, precisely how this is handled depends on the attribute handler for the specific attribute.

### Semantics

`outer_attr` applies to the surrounding scope, e.g., an outer attributes at the top level of a file applies to the whole module.

`inner_attr` applies to the following item.

Values are specified below or may be specified by tools. Incorrect values are warnings (not errors).

### Initial values

#### Outer attributes

- `settings`
- `defaultLengthUnit`: a length unit, `mm`, ...
- `defaultAngleUnit`: an angle unit, `deg`, `rad`
- `attrs`: identifier[], to register consumers of attributes
- `metadata`
- `kclVersion`: a semver string
- `title`: string
- `description`: string
- any other `ident: string` values
- `no_std` - no implicit import of the std prelude

Possibly:

- `warnings`
- `allow`: identifier | identifier[], silence warnings (or lints)
- `deny`: identifier | identifier[], make warnings (or lints) into errors

#### Inner attributes

- `import` of non-KCL files
- `format`: ident, one of `fbx`, `gltf`, `glb`, `obj`, `ply`, `sldprt`, `step`, `stl`
- `lengthUnit`: a length unit, `mm`, ...
- `coords`: ident, one of `zoo`, `opengl`, `vulkan`
- functions
- `impl`: `kcl` (default) or `std_rust` (body must be empty, implemented in Rust as part of KCL interpreter)


### Examples

```
@settings(defaultLengthUnit = in, attrs = [fmt])

import "foo.kcl"
@(lengthUnit = mm)
import "foo.obj"

@(impl = std_rust)
fn bar(@sketch, radius: number): Circle {}

@(fmt = ignore)
fn baz() {
@settings(...)
@fmt(ignore = true)
}
```


## Alternatives

- `#` and `#!` like Rust but without `[]`
- `#!settings(defaultLengthUnit = in)`, `#(lengthUnit = mm)`
- `#` instead of `@`
- `#settings(defaultLengthUnit = in)`, `#(lengthUnit = mm)`

```
#!settings(defaultLengthUnit = in)

import "foo.kcl"
#(lengthUnit = mm)
import "foo.obj"

#(impl = std_rust)
fn bar(@sketch, radius: number): Circle {}

fn baz() {
#!settings(...)
}
```

## Issues

- "Attributes" or "annotations"? I prefer the former, we've been using a mix, but mostly the latter. Other languages use either or both, no consensus.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd choose attributes too. It's pretty arbitrary, as long as we stick with one. The only theme I've seen maybe hinted at is that annotations have no runtime effect by default. But it's all just metadata.

- Syntax overlap with `@` for 'self'
- Remove `@` on self-arg once we've finished the kwarg migration
Comment on lines +117 to +118
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the @? I thought someone said that we needed a way to distinguish between a regular function and one that has a "self" parameter.

An alternative is how Python does it with a separator between positional and named parameters. I don't particularly like this, but it's optimized so that in the common case, the separator is rarely needed at all. For KCL, it would potentially allow us to free up the token.

If we need to keep it, couldn't we use basically any other token?

@outer

@!inner
fn foo(!positional: Plane, named: string): Sketch {}

// Attribute on the positional parameter.
fn foo(@!inner !positional: Plane, named: string): Sketch {}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"I thought someone said that we needed a way to distinguish between a regular function and one that has a "self" parameter." - hmm, yeah

"If we need to keep it, couldn't we use basically any other token?" - yes

- Discoverability and docs
- settings and attributes on imports are now documented, not sure if we want to document attributes in one place or in many places based on what they do. Some attributes designed for use in the standard library are probably better left undocumented.