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
2 changes: 1 addition & 1 deletion ephapax-linear/src/affine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl AffineChecker {
self.ctx.exit_region();
}

ExprKind::Borrow(inner) | ExprKind::Deref(inner) => {
ExprKind::Borrow { inner, .. } | ExprKind::Deref(inner) => {
self.walk_expr(inner);
}

Expand Down
2 changes: 1 addition & 1 deletion ephapax-linear/src/linear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ impl LinearChecker {
}

// --- Borrow/Deref: walk inner ---
ExprKind::Borrow(inner) | ExprKind::Deref(inner) => {
ExprKind::Borrow { inner, .. } | ExprKind::Deref(inner) => {
self.walk_expr(inner);
}

Expand Down
20 changes: 20 additions & 0 deletions examples/linear/16-shared-vs-exclusive-borrow.eph
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: PMPL-1.0-or-later
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
//
// Example: shared (`&`) vs exclusive (`&mut`) borrow parameters.
//
// At codegen these surface in `typedwasm.ownership` as `SharedBorrow`
// and `ExclBorrow` respectively. The L7 alias-exclusion verifier pass
// in typed-wasm fires on `ExclBorrow` parameters used more than once.

fn observe(buf: &String@r) -> I32 {
// `buf` is a shared borrow — multiple shared borrows of the same
// backing object may coexist. Emitted as SharedBorrow.
0
}

fn mutate(buf: &mut String@r) -> I32 {
// `buf` is an exclusive borrow — no other live borrow of the same
// backing object is permitted. Emitted as ExclBorrow.
0
}
2 changes: 1 addition & 1 deletion src/ephapax-analysis/src/escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl EscapeAnalysis {
Self::analyze_expr(inner, escaping, in_escaping_context);
}

ExprKind::Borrow(inner) => {
ExprKind::Borrow { inner, .. } => {
Self::analyze_expr(inner, escaping, in_escaping_context);
}

Expand Down
2 changes: 1 addition & 1 deletion src/ephapax-analysis/src/free_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl FreeVarAnalysis {

ExprKind::Drop(inner)
| ExprKind::Copy(inner)
| ExprKind::Borrow(inner)
| ExprKind::Borrow { inner, .. }
| ExprKind::Deref(inner)
| ExprKind::Fst(inner)
| ExprKind::Snd(inner)
Expand Down
2 changes: 1 addition & 1 deletion src/ephapax-analysis/src/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl LivenessAnalysis {

ExprKind::Drop(inner)
| ExprKind::Copy(inner)
| ExprKind::Borrow(inner)
| ExprKind::Borrow { inner, .. }
| ExprKind::Deref(inner)
| ExprKind::Fst(inner)
| ExprKind::Snd(inner)
Expand Down
10 changes: 8 additions & 2 deletions src/ephapax-desugar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,10 @@ impl Desugarer {
body: Box::new(self.desugar_expr(body)?),
},

SurfaceExprKind::Borrow(inner) => ExprKind::Borrow(Box::new(self.desugar_expr(inner)?)),
SurfaceExprKind::Borrow { inner, mutable } => ExprKind::Borrow {
inner: Box::new(self.desugar_expr(inner)?),
mutable: *mutable,
},
SurfaceExprKind::Deref(inner) => ExprKind::Deref(Box::new(self.desugar_expr(inner)?)),
SurfaceExprKind::Drop(inner) => ExprKind::Drop(Box::new(self.desugar_expr(inner)?)),
SurfaceExprKind::Copy(inner) => ExprKind::Copy(Box::new(self.desugar_expr(inner)?)),
Expand Down Expand Up @@ -553,7 +556,10 @@ impl Desugarer {
name: name.clone(),
inner: Box::new(self.desugar_ty(inner)?),
}),
SurfaceTy::Borrow(inner) => Ok(Ty::Borrow(Box::new(self.desugar_ty(inner)?))),
SurfaceTy::Borrow { inner, mutable } => Ok(Ty::Borrow {
inner: Box::new(self.desugar_ty(inner)?),
mutable: *mutable,
}),
SurfaceTy::Var(v) => Ok(Ty::Var(v.clone())),
SurfaceTy::List(inner) => Ok(Ty::List(Box::new(self.desugar_ty(inner)?))),
SurfaceTy::Tuple(elements) => {
Expand Down
4 changes: 2 additions & 2 deletions src/ephapax-interp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ impl Value {
param: Box::new(param_ty.clone()),
ret: Box::new(Ty::Base(BaseTy::Unit)), // Unknown without evaluation
},
Value::Borrow(inner) => Ty::Borrow(Box::new(inner.to_type())),
Value::Borrow(inner) => Ty::Borrow { inner: Box::new(inner.to_type()), mutable: false },
}
}
}
Expand Down Expand Up @@ -436,7 +436,7 @@ impl Interpreter {
else_branch,
} => self.eval_if(cond, then_branch, else_branch),
ExprKind::Region { name, body } => self.eval_region(name, body),
ExprKind::Borrow(inner) => self.eval_borrow(inner),
ExprKind::Borrow { inner, .. } => self.eval_borrow(inner),
ExprKind::Deref(inner) => self.eval_deref(inner),
ExprKind::Drop(inner) => self.eval_drop(inner),
ExprKind::Copy(inner) => self.eval_copy(inner),
Expand Down
30 changes: 24 additions & 6 deletions src/ephapax-ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,10 @@ fn expr_to_sexpr(expr: &Expr) -> SExpr {
SExpr::Atom(escape_atom(name)),
expr_to_sexpr(body),
]),
ExprKind::Borrow(inner) => {
SExpr::List(vec![SExpr::Atom("borrow".into()), expr_to_sexpr(inner)])
}
ExprKind::Borrow { inner, mutable } => SExpr::List(vec![
SExpr::Atom(if *mutable { "borrow-mut" } else { "borrow" }.into()),
expr_to_sexpr(inner),
]),
ExprKind::Deref(inner) => {
SExpr::List(vec![SExpr::Atom("deref".into()), expr_to_sexpr(inner)])
}
Expand Down Expand Up @@ -735,7 +736,14 @@ fn decode_expr(expr: &SExpr) -> Result<Expr, SExprError> {
name: SmolStr::new(atom_string(&list[1])?),
body: Box::new(decode_expr(&list[2])?),
},
"borrow" => ExprKind::Borrow(Box::new(decode_expr(&list[1])?)),
"borrow" => ExprKind::Borrow {
inner: Box::new(decode_expr(&list[1])?),
mutable: false,
},
"borrow-mut" => ExprKind::Borrow {
inner: Box::new(decode_expr(&list[1])?),
mutable: true,
},
"deref" => ExprKind::Deref(Box::new(decode_expr(&list[1])?)),
"drop" => ExprKind::Drop(Box::new(decode_expr(&list[1])?)),
"copy" => ExprKind::Copy(Box::new(decode_expr(&list[1])?)),
Expand Down Expand Up @@ -851,7 +859,10 @@ fn ty_to_sexpr(ty: &Ty) -> SExpr {
SExpr::Atom(escape_atom(name)),
ty_to_sexpr(inner),
]),
Ty::Borrow(inner) => SExpr::List(vec![SExpr::Atom("borrow".into()), ty_to_sexpr(inner)]),
Ty::Borrow { inner, mutable } => SExpr::List(vec![
SExpr::Atom(if *mutable { "borrow-mut" } else { "borrow" }.into()),
ty_to_sexpr(inner),
]),
Ty::Var(v) => SExpr::List(vec![SExpr::Atom("var".into()), SExpr::Atom(escape_atom(v))]),
Ty::List(inner) => SExpr::List(vec![SExpr::Atom("list".into()), ty_to_sexpr(inner)]),
Ty::Tuple(elem_types) => {
Expand Down Expand Up @@ -926,7 +937,14 @@ fn decode_ty(expr: &SExpr) -> Result<Ty, SExprError> {
name: SmolStr::new(atom_string(&list[1])?),
inner: Box::new(decode_ty(&list[2])?),
}),
"borrow" => Ok(Ty::Borrow(Box::new(decode_ty(&list[1])?))),
"borrow" => Ok(Ty::Borrow {
inner: Box::new(decode_ty(&list[1])?),
mutable: false,
}),
"borrow-mut" => Ok(Ty::Borrow {
inner: Box::new(decode_ty(&list[1])?),
mutable: true,
}),
"var" => Ok(Ty::Var(SmolStr::new(atom_string(&list[1])?))),
_ => Err(SExprError::Invalid("unknown type tag".into())),
}
Expand Down
4 changes: 2 additions & 2 deletions src/ephapax-lsp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ fn format_ty(ty: &Ty) -> String {
Ty::Ref { inner, .. } => format!("Ref({})", format_ty(inner)),
Ty::String(region) => format!("String@{}", region),
Ty::Region { name, inner } => format!("Region({}, {})", name, format_ty(inner)),
Ty::Borrow(inner) => format!("&{}", format_ty(inner)),
Ty::Borrow { inner, mutable } => format!("&{}{}", if *mutable { "mut " } else { "" }, format_ty(inner)),
Ty::Var(name) => name.to_string(),
Ty::ForAll { var, body } => format!("forall {}. {}", var, format_ty(body)),
Ty::Unif(id) => format!("?{}", id),
Expand Down Expand Up @@ -847,7 +847,7 @@ fn find_let_binding_span(expr: &Expr, target: &str) -> Option<Span> {
| ExprKind::Inr { value: inner, .. }
| ExprKind::Drop(inner)
| ExprKind::Copy(inner)
| ExprKind::Borrow(inner)
| ExprKind::Borrow { inner, .. }
| ExprKind::Deref(inner)
| ExprKind::UnaryOp { operand: inner, .. }
| ExprKind::StringLen(inner) => find_let_binding_span(inner, target),
Expand Down
10 changes: 6 additions & 4 deletions src/ephapax-parser/src/ephapax.pest
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ unit_ty = { "()" }

string_ty = { "String" ~ ("@" ~ identifier)? }

borrow_ty = { "&" ~ type_atom }
borrow_ty = { "&" ~ mut_marker? ~ type_atom }

mut_marker = @{ "mut" ~ !(ASCII_ALPHANUMERIC | "_") }

list_ty = { "[" ~ ty ~ "]" }

Expand Down Expand Up @@ -402,7 +404,7 @@ expr_list = { expression ~ ("," ~ expression)* }
inl_expr = { "inl" ~ "[" ~ ty ~ "]" ~ "(" ~ expression ~ ")" }
inr_expr = { "inr" ~ "[" ~ ty ~ "]" ~ "(" ~ expression ~ ")" }

borrow_expr = { "&" ~ unary_expr }
borrow_expr = { "&" ~ mut_marker? ~ unary_expr }

fst_expr = { "fst" ~ "(" ~ expression ~ ")" }
snd_expr = { "snd" ~ "(" ~ expression ~ ")" }
Expand Down Expand Up @@ -451,7 +453,7 @@ keyword_boundary = {
| "region" | "case" | "of" | "inl" | "inr" | "end"
| "fst" | "snd" | "drop" | "copy" | "type" | "data" | "match"
| "extern"
| "true" | "false" | "pub" | "import" | "module" | "linear"
| "true" | "false" | "pub" | "import" | "module" | "linear" | "mut"
| "perform" | "handle" | "with" | "return" | "resume" | "once" | "multi"
| "Bool" | "I32" | "I64" | "F32" | "F64" | "String"
) ~ !(ASCII_ALPHANUMERIC | "_")
Expand All @@ -462,7 +464,7 @@ keyword = {
| "region" | "case" | "of" | "inl" | "inr" | "end"
| "fst" | "snd" | "drop" | "copy" | "type" | "data" | "match"
| "extern"
| "true" | "false" | "pub" | "import" | "module" | "linear"
| "true" | "false" | "pub" | "import" | "module" | "linear" | "mut"
| "perform" | "handle" | "with" | "return" | "resume" | "once" | "multi"
| "Bool" | "I32" | "I64" | "F32" | "F64" | "String"
}
Expand Down
63 changes: 48 additions & 15 deletions src/ephapax-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,13 +583,22 @@ fn parse_type_atom(pair: pest::iterators::Pair<Rule>) -> Result<Ty, ParseError>
Ok(Ty::String(region))
}
Rule::borrow_ty => {
let inner_ty = parse_type_atom(
inner
.into_inner()
.next()
.ok_or_else(|| ParseError::missing("borrowed type"))?,
)?;
Ok(Ty::Borrow(Box::new(inner_ty)))
let mut children = inner.into_inner();
let first = children
.next()
.ok_or_else(|| ParseError::missing("borrowed type"))?;
let (mutable, ty_pair) = if first.as_rule() == Rule::mut_marker {
(
true,
children
.next()
.ok_or_else(|| ParseError::missing("borrowed type"))?,
)
} else {
(false, first)
};
let inner_ty = parse_type_atom(ty_pair)?;
Ok(Ty::Borrow { inner: Box::new(inner_ty), mutable })
}
Rule::list_ty => {
let elem_ty = parse_type(
Expand Down Expand Up @@ -1748,13 +1757,25 @@ fn parse_atom_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expr, ParseError
))
}
Rule::borrow_expr => {
let inner_expr = parse_unary_expr(
inner
.into_inner()
.next()
.ok_or_else(|| ParseError::missing("borrow operand"))?,
)?;
Ok(Expr::new(ExprKind::Borrow(Box::new(inner_expr)), span))
let mut children = inner.into_inner();
let first = children
.next()
.ok_or_else(|| ParseError::missing("borrow operand"))?;
let (mutable, operand_pair) = if first.as_rule() == Rule::mut_marker {
(
true,
children
.next()
.ok_or_else(|| ParseError::missing("borrow operand"))?,
)
} else {
(false, first)
};
let inner_expr = parse_unary_expr(operand_pair)?;
Ok(Expr::new(
ExprKind::Borrow { inner: Box::new(inner_expr), mutable },
span,
))
}
Rule::fst_expr => {
let inner_expr = parse_expression(
Expand Down Expand Up @@ -2106,13 +2127,25 @@ mod tests {
#[test]
fn test_parse_borrow() {
let expr = parse_ok("&x");
if let ExprKind::Borrow(inner) = expr.kind {
if let ExprKind::Borrow { inner, mutable } = expr.kind {
assert!(matches!(inner.kind, ExprKind::Var(_)));
assert!(!mutable, "`&x` must parse as shared (mutable=false)");
} else {
panic!("Expected borrow");
}
}

#[test]
fn test_parse_mut_borrow() {
let expr = parse_ok("&mut x");
if let ExprKind::Borrow { inner, mutable } = expr.kind {
assert!(matches!(inner.kind, ExprKind::Var(_)));
assert!(mutable, "`&mut x` must parse as exclusive (mutable=true)");
} else {
panic!("Expected mut borrow");
}
}

#[test]
fn test_parse_drop() {
let expr = parse_ok("drop(x)");
Expand Down
46 changes: 32 additions & 14 deletions src/ephapax-parser/src/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1490,14 +1490,23 @@ fn parse_atom_expr(pair: pest::iterators::Pair<Rule>) -> Result<SurfaceExpr, Par
}

Rule::borrow_expr => {
let inner_expr = parse_unary_expr(
inner
.into_inner()
.next()
.ok_or_else(|| ParseError::missing("borrow"))?,
)?;
let mut children = inner.into_inner();
let first = children
.next()
.ok_or_else(|| ParseError::missing("borrow"))?;
let (mutable, operand_pair) = if first.as_rule() == Rule::mut_marker {
(
true,
children
.next()
.ok_or_else(|| ParseError::missing("borrow"))?,
)
} else {
(false, first)
};
let inner_expr = parse_unary_expr(operand_pair)?;
Ok(SurfaceExpr::new(
SurfaceExprKind::Borrow(Box::new(inner_expr)),
SurfaceExprKind::Borrow { inner: Box::new(inner_expr), mutable },
span,
))
}
Expand Down Expand Up @@ -1787,13 +1796,22 @@ fn parse_type_atom(pair: pest::iterators::Pair<Rule>) -> Result<SurfaceTy, Parse
Ok(SurfaceTy::String(region))
}
Rule::borrow_ty => {
let inner_ty = parse_type_atom(
inner
.into_inner()
.next()
.ok_or_else(|| ParseError::missing("borrow inner"))?,
)?;
Ok(SurfaceTy::Borrow(Box::new(inner_ty)))
let mut children = inner.into_inner();
let first = children
.next()
.ok_or_else(|| ParseError::missing("borrow inner"))?;
let (mutable, ty_pair) = if first.as_rule() == Rule::mut_marker {
(
true,
children
.next()
.ok_or_else(|| ParseError::missing("borrow inner"))?,
)
} else {
(false, first)
};
let inner_ty = parse_type_atom(ty_pair)?;
Ok(SurfaceTy::Borrow { inner: Box::new(inner_ty), mutable })
}
Rule::list_ty => {
let elem_ty = parse_type(
Expand Down
2 changes: 1 addition & 1 deletion src/ephapax-repl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ fn format_type(ty: &Ty) -> String {
Ty::Fun { param, ret } => format!("{} -> {}", format_type(param), format_type(ret)),
Ty::Prod { left, right } => format!("({}, {})", format_type(left), format_type(right)),
Ty::Sum { left, right } => format!("{} + {}", format_type(left), format_type(right)),
Ty::Borrow(inner) => format!("&{}", format_type(inner)),
Ty::Borrow { inner, mutable } => format!("&{}{}", if *mutable { "mut " } else { "" }, format_type(inner)),
Ty::List(elem_ty) => format!("[{}]", format_type(elem_ty)),
Ty::Tuple(elem_types) => {
let types_str = elem_types
Expand Down
Loading