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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,26 @@ including on the command line:
arc run --cell 'via(ViaParams { layer: "met1", size: Size { w: 100., h: 50. }, n: 1 })'
```

A `let` takes a struct apart with a pattern that names the struct and binds its
fields. A bare `name` binds the field of that name, `field: other` binds it
under another name, and `field: _` drops it. A pattern must name every field
unless it ends in `..`, and field patterns do not nest. At the top of a cell,
each name a pattern binds is a field of the cell, like any other `let`. A
`match` on a struct takes the same patterns:

```rust
fn area(s: Size) -> Float {
let Size { w, h: height } = s;
w * height
}

cell via_pair(p: ViaParams) {
let ViaParams { layer, size, .. } = p;
let left = rect(layer, x0=0., y0=0., w=size.w, h=size.h);
let right = rect(layer, x0=2. * size.w, y0=0., w=size.w, h=size.h);
}
```

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
Expand Down
15 changes: 5 additions & 10 deletions crates/analyzer/src/navigation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,17 +611,12 @@ 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, .. }`.
// A field is offered because a struct or variant pattern binds fields
// by name, as in `Shape::Box { w, .. }`.
CompletionSite::Pattern => match candidate.kind {
Kind::Enum | Kind::Variant | Kind::Module | Kind::Field => true,
Kind::Enum | Kind::Variant | Kind::Struct | Kind::Module | Kind::Field => true,
Kind::Keyword => candidate.label == "_",
Kind::Function
| Kind::Cell
| Kind::Variable
| Kind::Parameter
| Kind::Struct
| Kind::Type => false,
Kind::Function | Kind::Cell | Kind::Variable | Kind::Parameter | Kind::Type => false,
},
CompletionSite::ImportPath => candidate.kind == Kind::Module,
CompletionSite::Keyword(keyword) => {
Expand Down Expand Up @@ -1249,7 +1244,7 @@ mod tests {
assert_eq!(labels(CompletionSite::ImportPath), ["lib"]);
assert_eq!(
labels(CompletionSite::Pattern),
["Mode", "lib", "_", "Some"]
["Mode", "Size", "lib", "_", "Some"]
);
// The statement candidates, plus `else`.
assert_eq!(
Expand Down
6 changes: 2 additions & 4 deletions crates/analyzer/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,10 +742,8 @@ impl LangServer for State {
let names = scope
.stmts
.iter()
.filter_map(|statement| match statement {
argonc::ast::Statement::LetBinding(binding) => Some(binding.name.name.as_str()),
_ => None,
})
.flat_map(|statement| statement.let_names())
.map(|name| name.name.as_str())
.collect::<std::collections::HashSet<_>>();
let var_name = (0..)
.map(|index| format!("inst{index}"))
Expand Down
91 changes: 84 additions & 7 deletions crates/compiler/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,33 @@ pub struct Scope<S, T: AstMetadata> {

#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub enum Statement<S, T: AstMetadata> {
Expr { value: Expr<S, T>, semicolon: bool },
Expr {
value: Expr<S, T>,
semicolon: bool,
},
LetBinding(LetBinding<S, T>),
/// `let S { f, g: name, .. } = value;`
LetPattern(LetPattern<S, T>),
ForLoop(ForLoop<S, T>),
}

impl<S, T: AstMetadata> Statement<S, T> {
/// The names a `let` statement binds, in source order; empty for any
/// other statement.
pub fn let_names(&self) -> Vec<&Ident<S, T>> {
match self {
Self::LetBinding(binding) => vec![&binding.name],
Self::LetPattern(binding) => binding
.pattern
.bindings()
.into_iter()
.map(|(name, _)| name)
.collect(),
Self::Expr { .. } | Self::ForLoop(_) => Vec::new(),
}
}
}

#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct LetBinding<S, T: AstMetadata> {
pub name: Ident<S, T>,
Expand All @@ -259,6 +281,15 @@ pub struct LetBinding<S, T: AstMetadata> {
pub span: cfgrammar::Span,
}

/// A `let` that destructures its value with a pattern. Each name the pattern
/// binds carries its own metadata.
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct LetPattern<S, T: AstMetadata> {
pub pattern: Pattern<S, T>,
pub value: Expr<S, T>,
pub span: cfgrammar::Span,
}

#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct ForLoop<S, T: AstMetadata> {
pub var: Ident<S, T>,
Expand Down Expand Up @@ -380,8 +411,9 @@ 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 {
/// A struct, `S { f, g: name, .. }`, or an enum variant with named
/// fields, `E::V { f, g: name, .. }`.
Struct {
path: IdentPath<S, T>,
fields: Vec<FieldPattern<S, T>>,
/// Whether the pattern ended in `..`, ignoring the fields not listed.
Expand All @@ -390,7 +422,7 @@ pub enum Pattern<S, T: AstMetadata> {
},
}

/// One `field: pattern` entry of a [`Pattern::StructVariant`].
/// One `field: pattern` entry of a [`Pattern::Struct`].
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct FieldPattern<S, T: AstMetadata> {
pub name: Ident<S, T>,
Expand All @@ -405,9 +437,40 @@ impl<S, T: AstMetadata> Pattern<S, T> {
match self {
Self::Wildcard { span } => *span,
Self::Binding { name, .. } => name.span,
Self::Variant { span, .. } | Self::StructVariant { span, .. } => *span,
Self::Variant { span, .. } | Self::Struct { span, .. } => *span,
}
}

/// The bindings this pattern introduces, in source order.
///
/// A bare name counts as a binding even where it may turn out to name a
/// unit variant, which only the type checker can decide.
pub fn bindings(&self) -> Vec<(&Ident<S, T>, &T::PatternBinding)> {
let mut out = Vec::new();
self.collect_bindings(&mut out);
out
}

fn collect_bindings<'p>(&'p self, out: &mut Vec<(&'p Ident<S, T>, &'p T::PatternBinding)>) {
match self {
Self::Wildcard { .. } => {}
Self::Binding { name, metadata } => out.push((name, metadata)),
Self::Variant { fields, .. } => {
fields.iter().for_each(|field| field.collect_bindings(out));
}
Self::Struct { fields, .. } => {
fields
.iter()
.for_each(|field| field.pattern.collect_bindings(out));
}
}
}

/// Whether the pattern is a name or `_`, which matches any value once type
/// checking has resolved bare names that are unit variants.
pub fn is_catch_all(&self) -> bool {
matches!(self, Self::Wildcard { .. } | Self::Binding { .. })
}
}

/// A binary operation expression.
Expand Down Expand Up @@ -1065,6 +1128,7 @@ pub trait AstTransformer {
semicolon: *semicolon,
},
Statement::LetBinding(l) => Statement::LetBinding(self.transform_let_binding(l)),
Statement::LetPattern(l) => Statement::LetPattern(self.transform_let_pattern(l)),
Statement::ForLoop(l) => Statement::ForLoop(self.transform_for_loop(l)),
}
}
Expand All @@ -1084,6 +1148,19 @@ pub trait AstTransformer {
span: input.span,
}
}
fn transform_let_pattern(
&mut self,
input: &LetPattern<Self::InputS, Self::InputMetadata>,
) -> LetPattern<Self::OutputS, Self::OutputMetadata> {
// The value is visited first, since the names are bound after it.
let value = self.transform_expr(&input.value);
let pattern = self.transform_pattern(&input.pattern);
LetPattern {
pattern,
value,
span: input.span,
}
}
fn transform_for_loop(
&mut self,
input: &ForLoop<Self::InputS, Self::InputMetadata>,
Expand Down Expand Up @@ -1162,12 +1239,12 @@ pub trait AstTransformer {
.collect(),
span: *span,
},
Pattern::StructVariant {
Pattern::Struct {
path,
fields,
rest,
span,
} => Pattern::StructVariant {
} => Pattern::Struct {
path: self.transform_ident_path(path),
fields: fields
.iter()
Expand Down
Loading
Loading