Skip to content
Merged
Show file tree
Hide file tree
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
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ including on the command line:
arc run --cell 'via(ViaParams { layer: "met1", size: Size { w: 100., h: 50. }, n: 1 })'
```

Enum variants may carry values. A tuple variant is constructed like a call and
taken apart by a `match` pattern that binds its payload; `_` skips an element,
and a bare name or `_` arm matches anything. Every variant is also an item that
`use` can import:
Enum variants may carry values, either as a tuple or as named fields. A tuple
variant is constructed like a call and taken apart by a `match` pattern that
binds its payload; `_` skips an element, and a bare name or `_` arm matches
anything. Every variant is also an item that `use` can import:

```rust
enum Shape {
Expand All @@ -175,6 +175,36 @@ fn width(s: Shape) -> Float {
}
```

A variant with named fields is written and constructed like a struct, with the
same shorthand, and matched by a pattern that names the fields it binds. A
pattern must name every field unless it ends in `..`, `field: name` renames a
binding, and `field: _` drops one. Unlike a struct literal, a variant takes no
`..base`, and like a struct literal it must be parenthesized in an `if`
condition, a `match` scrutinee, or a `for` sequence:

```rust
enum Shape {
Circle { r: Float },
Box { w: Float, h: Float },
Empty,
}

fn width(s: Shape) -> Float {
match s {
Shape::Circle { r } => 2. * r,
Shape::Box { w, .. } => w,
Shape::Empty => 0.,
}
}

cell top() {
let w = 300.;
let h = 20.;
let boxed = Shape::Box { w, h };
let r = rect("met1", x0=0., y0=0., w=width(boxed), h=h);
}
```

Structs, enums, functions, and cells take type parameters, written in angle
brackets after the name as in Rust. A type parameter is opaque: a value of
type `T` can be stored, passed, and returned, but not added, compared, cast,
Expand Down
5 changes: 3 additions & 2 deletions crates/analyzer/src/navigation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,15 +611,16 @@ fn completion_allowed(candidate: &CompletionCandidate, site: CompletionSite) ->
candidate.kind,
Kind::Cell | Kind::Enum | Kind::Struct | Kind::Type
),
// A field is offered because a variant pattern binds its payload by
// name, as in `Shape::Box { w, .. }`.
CompletionSite::Pattern => match candidate.kind {
Kind::Enum | Kind::Variant | Kind::Module => true,
Kind::Enum | Kind::Variant | Kind::Module | Kind::Field => true,
Kind::Keyword => candidate.label == "_",
Kind::Function
| Kind::Cell
| Kind::Variable
| Kind::Parameter
| Kind::Struct
| Kind::Field
| Kind::Type => false,
},
CompletionSite::ImportPath => candidate.kind == Kind::Module,
Expand Down
44 changes: 32 additions & 12 deletions crates/arc/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use anyhow::{Context, Result, bail};
use arcstr::Substr;
use argonc::{
WorkspaceConfig,
ast::{ArgDecl, Decl, ModPath, TyParam, TySpec, TySpecKind},
ast::{ArgDecl, Decl, ModPath, TyParam, TySpec, TySpecKind, VariantPayload},
parse::{self, AnnotatedParseAst, ParseMetadata, WorkspaceParseAst},
};

Expand Down Expand Up @@ -339,19 +339,35 @@ fn render_module(
.variants
.iter()
.map(|variant| {
let payload = (!variant.payload.is_empty()).then(|| {
let types = variant
.payload
.iter()
.map(|ty| render_type(ty, module.path, targets))
.collect::<Vec<_>>()
.join(", ");
format!("({types})")
});
let payload = match &variant.payload {
VariantPayload::Tuple(payload) if payload.is_empty() => String::new(),
VariantPayload::Tuple(payload) => {
let types = payload
.iter()
.map(|ty| render_type(ty, module.path, targets))
.collect::<Vec<_>>()
.join(", ");
format!("({types})")
}
VariantPayload::Struct(fields) => {
let fields = fields
.iter()
.map(|field| {
format!(
"{}: {}",
escape(&field.name.name),
render_type(&field.ty, module.path, targets)
)
})
.collect::<Vec<_>>()
.join(", ");
format!(" {{ {fields} }}")
}
};
format!(
"<li><code>{}{}</code></li>",
escape(&variant.name.name),
payload.unwrap_or_default()
payload
)
})
.collect::<Vec<_>>()
Expand Down Expand Up @@ -644,7 +660,7 @@ mod tests {
fs::write(directory.path().join("Argon.toml"), "name = \"demo\"\n").unwrap();
fs::write(
directory.path().join("lib.ar"),
"//! Demo cells.\n/// Routing modes.\nenum Mode { Fast, Quiet, }\n/// Builds a route.\n/// # Arguments\n/// - `mode`: routing mode.\ncell route(mode: Mode) {}\n/// A mode or nothing.\nenum Maybe<T> { Just(T, Mode), Nothing, }\n/// Picks a mode.\nfn pick<T>(m: Maybe<T>, n: Option<Int>) -> Mode { Mode::Fast }\n",
"//! Demo cells.\n/// Routing modes.\nenum Mode { Fast, Quiet, }\n/// Builds a route.\n/// # Arguments\n/// - `mode`: routing mode.\ncell route(mode: Mode) {}\n/// A mode or nothing.\nenum Maybe<T> { Just(T, Mode), Sized { width: Int, mode: Mode }, Nothing, }\n/// Picks a mode.\nfn pick<T>(m: Maybe<T>, n: Option<Int>) -> Mode { Mode::Fast }\n",
)
.unwrap();
let library = Library::load(directory.path().join("Argon.toml")).unwrap();
Expand All @@ -664,6 +680,10 @@ mod tests {
"<li><code>Just(<span class=\"type\">T</span>, <a class=\"type\" href=\"module-root.html#enum.Mode\">Mode</a>)</code></li>"
));
assert!(page.contains("<li><code>Nothing</code></li>"));
// A variant with named fields renders its fields, not a tuple.
assert!(page.contains(
"<li><code>Sized { width: <span class=\"type\">Int</span>, mode: <a class=\"type\" href=\"module-root.html#enum.Mode\">Mode</a> }</code></li>"
));
assert!(page.contains("<span class=\"name\">pick</span>&lt;T&gt;("));
assert!(page.contains(
"<a class=\"type\" href=\"module-root.html#enum.Maybe\">Maybe</a>&lt;<span class=\"type\">T</span>&gt;"
Expand Down
40 changes: 40 additions & 0 deletions crates/arc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,46 @@ cell top() {
);
}

/// A variant's braces nest like any other, so a declaration and a pattern
/// that spans lines indent from the enclosing block.
#[test]
fn formats_struct_variant_braces() {
let source = r#"
enum Shape {
Circle { r: Float },
Box {
w: Float,
h: Float,
},
}

fn width(s: Shape) -> Float {
match s {
Shape::Circle { r } => r,
Shape::Box { w, .. } => w,
}
}
"#;
assert_eq!(
format_source(source),
r#"enum Shape {
Circle { r: Float },
Box {
w: Float,
h: Float,
},
}

fn width(s: Shape) -> Float {
match s {
Shape::Circle { r } => r,
Shape::Box { w, .. } => w,
}
}
"#
);
}

#[test]
fn formatting_check_does_not_write_files() {
let directory = tempfile::tempdir().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/compiler/src/ast/annotated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl<S, T: AstMetadata> AstTransformer for AstAnnotationPass<S, T> {
&mut self,
input: &super::EnumVariant<Self::InputS, Self::InputMetadata>,
_name: &super::Ident<Self::OutputS, Self::OutputMetadata>,
_payload: &[super::TySpec<Self::OutputS, Self::OutputMetadata>],
_payload: &super::VariantPayload<Self::OutputS, Self::OutputMetadata>,
) -> <Self::OutputMetadata as AstMetadata>::EnumVariant {
input.metadata.clone()
}
Expand Down
99 changes: 90 additions & 9 deletions crates/compiler/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,44 @@ pub struct EnumDecl<S, T: AstMetadata> {
pub metadata: T::EnumDecl,
}

/// One variant of an enum, with the types of its payload: `Some(T)` or `None`.
/// One variant of an enum, with the types of its payload: `Some(T)`,
/// `Circle { r: Float }`, or `None`.
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct EnumVariant<S, T: AstMetadata> {
pub name: Ident<S, T>,
pub payload: Vec<TySpec<S, T>>,
pub payload: VariantPayload<S, T>,
pub span: cfgrammar::Span,
pub metadata: T::EnumVariant,
}

/// The declared payload of an [`EnumVariant`].
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub enum VariantPayload<S, T: AstMetadata> {
/// Positional types, `V(A, B)`; empty for a unit variant.
Tuple(Vec<TySpec<S, T>>),
/// Named fields, `V { a: A, b: B }`.
Struct(Vec<StructField<S, T>>),
}

impl<S, T: AstMetadata> VariantPayload<S, T> {
/// Whether this variant carries nothing.
pub fn is_unit(&self) -> bool {
matches!(self, Self::Tuple(payload) if payload.is_empty())
}

/// The payload types in declaration order.
pub fn tys(&self) -> impl Iterator<Item = &TySpec<S, T>> {
let (tuple, fields) = match self {
Self::Tuple(payload) => (Some(payload), None),
Self::Struct(fields) => (None, Some(fields)),
};
tuple
.into_iter()
.flatten()
.chain(fields.into_iter().flatten().map(|field| &field.ty))
}
}

#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct StructDecl<S, T: AstMetadata> {
pub name: Ident<S, T>,
Expand Down Expand Up @@ -351,14 +380,32 @@ pub enum Pattern<S, T: AstMetadata> {
fields: Vec<Pattern<S, T>>,
span: cfgrammar::Span,
},
/// An enum variant with named fields, `E::V { f, g: name, .. }`.
StructVariant {
path: IdentPath<S, T>,
fields: Vec<FieldPattern<S, T>>,
/// Whether the pattern ended in `..`, ignoring the fields not listed.
rest: bool,
span: cfgrammar::Span,
},
}

/// One `field: pattern` entry of a [`Pattern::StructVariant`].
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct FieldPattern<S, T: AstMetadata> {
pub name: Ident<S, T>,
pub pattern: Pattern<S, T>,
/// Whether the field was written as a bare `f`, standing for `f: f`.
pub shorthand: bool,
pub span: cfgrammar::Span,
}

impl<S, T: AstMetadata> Pattern<S, T> {
pub fn span(&self) -> cfgrammar::Span {
match self {
Self::Wildcard { span } => *span,
Self::Binding { name, .. } => name.span,
Self::Variant { span, .. } => *span,
Self::Variant { span, .. } | Self::StructVariant { span, .. } => *span,
}
}
}
Expand Down Expand Up @@ -572,7 +619,7 @@ pub trait AstTransformer {
&mut self,
input: &EnumVariant<Self::InputS, Self::InputMetadata>,
name: &Ident<Self::OutputS, Self::OutputMetadata>,
payload: &[TySpec<Self::OutputS, Self::OutputMetadata>],
payload: &VariantPayload<Self::OutputS, Self::OutputMetadata>,
) -> <Self::OutputMetadata as AstMetadata>::EnumVariant;
fn dispatch_pattern_binding(
&mut self,
Expand Down Expand Up @@ -861,11 +908,20 @@ pub trait AstTransformer {
input: &EnumVariant<Self::InputS, Self::InputMetadata>,
) -> EnumVariant<Self::OutputS, Self::OutputMetadata> {
let name = self.transform_ident(&input.name);
let payload = input
.payload
.iter()
.map(|ty| self.transform_ty_spec(ty))
.collect_vec();
let payload = match &input.payload {
VariantPayload::Tuple(payload) => VariantPayload::Tuple(
payload
.iter()
.map(|ty| self.transform_ty_spec(ty))
.collect_vec(),
),
VariantPayload::Struct(fields) => VariantPayload::Struct(
fields
.iter()
.map(|field| self.transform_struct_field(field))
.collect_vec(),
),
};
let metadata = self.dispatch_enum_variant(input, &name, &payload);
EnumVariant {
name,
Expand Down Expand Up @@ -1106,6 +1162,31 @@ pub trait AstTransformer {
.collect(),
span: *span,
},
Pattern::StructVariant {
path,
fields,
rest,
span,
} => Pattern::StructVariant {
path: self.transform_ident_path(path),
fields: fields
.iter()
.map(|field| self.transform_field_pattern(field))
.collect(),
rest: *rest,
span: *span,
},
}
}
fn transform_field_pattern(
&mut self,
input: &FieldPattern<Self::InputS, Self::InputMetadata>,
) -> FieldPattern<Self::OutputS, Self::OutputMetadata> {
FieldPattern {
name: self.transform_ident(&input.name),
pattern: self.transform_pattern(&input.pattern),
shorthand: input.shorthand,
span: input.span,
}
}
fn transform_bin_op_expr(
Expand Down
18 changes: 18 additions & 0 deletions crates/compiler/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,24 @@ mod tests {
assert_eq!(rect.x1.0, 20.);
}

#[test]
fn execution_evaluates_a_struct_variant_cell_argument() {
let source = temp_source(
"struct-variant-args",
"enum Mode { Sized { w: Float, h: Float }, Fast, }\n\
cell top(m: Mode) {\n\
let w = match m { Mode::Sized { w, .. } => w, Mode::Fast => 10., };\n\
let r = rect(\"met1\", x0=0., y0=0., x1=w, y1=10.);\n\
}\n",
);
let rect = compiled_rect(
"struct-variant",
source,
"top(Mode::Sized { h: 1., w: 40. })",
);
assert_eq!(rect.x1.0, 40.);
}

#[test]
fn execution_evaluates_an_option_cell_argument() {
let source = temp_source(
Expand Down
Loading
Loading