Skip to content

Commit d848c71

Browse files
authored
Merge pull request #19 from jmecom/jm/capability-keyword
Jm/capability keyword
2 parents f3fb0f9 + f461f5d commit d848c71

56 files changed

Lines changed: 2723 additions & 2316 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ATTENUATION.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,23 @@ Here’s a clean way to model the same thing in capc with what you’ve already
44

55
1) Make “caps” affine + non-forgeable
66

7-
Keep using opaque struct as “this is a capability/handle.” That gives you:
7+
Use `capability struct` as “this is a capability/handle.” That gives you:
88
• cannot copy (your Token test)
9-
• cannot fabricate (opaque means no public fields / no user construction unless you allow Cap{} for opaques — you probably shouldn’t)
9+
• cannot fabricate (capability types are opaque: no public fields / no user construction outside the defining module)
1010

11-
Also: your current is_affine_type doesn’t automatically make sys.* opaque types affine. If RootCap is supposed to be the “source of authority,” it should be affine too (Austral’s root capability is special and is threaded through main). 
12-
So: add it to AFFINE_ROOTS (whatever its fully-qualified name is in your world, e.g. "sys.RootCap" or "rc.RootCap").
11+
Capability types default to affine unless marked `copy` or `linear`.
1312

1413
2) Encode attenuation as “only downward constructors”
1514

1615
Design the stdlib so the only way to get a capability is from a stronger one, and the API only lets you narrow.
1716

1817
A minimal pattern:
1918

20-
opaque struct RootCap
21-
opaque struct Filesystem
22-
opaque struct Dir
23-
opaque struct FileRead
24-
opaque struct FileWrite
19+
capability struct RootCap
20+
capability struct Filesystem
21+
capability struct Dir
22+
capability struct FileRead
23+
capability struct FileWrite
2524

2625
module fs {
2726
// mint broad cap from root (borrow root so you can mint others too)
@@ -49,7 +48,7 @@ Notes:
4948

5049
For a capability/handle, this is exactly the point:
5150

52-
opaque struct Token
51+
capability struct Token
5352

5453
pub fn main() -> i32 {
5554
let t = Token{}
@@ -58,7 +57,7 @@ pub fn main() -> i32 {
5857
return 0
5958
}
6059

61-
It’s only “too restrictive” if Token is meant to be data (copyable value). In that case, don’t make it opaque, or give it a non-affine representation (plain struct of copyable fields).
60+
It’s only “too restrictive” if Token is meant to be data (copyable value). In that case, don’t make it capability/opaque, or give it a non-affine representation (plain struct of copyable fields).
6261

6362
4) The tests that actually prove “caps can’t be reused”
6463

TUTORIAL.md

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ match flag {
125125

126126
## 6) Capabilities and attenuation
127127

128-
Capabilities live in `sys.*` and are opaque. You can only get them from `RootCap`.
128+
Capabilities live in `sys.*` and are declared with the `capability` keyword (capability types are opaque). You can only get them from `RootCap`.
129129

130130
```cap
131131
module read_config
@@ -146,28 +146,64 @@ pub fn main(rc: RootCap) -> i32 {
146146

147147
This is attenuation: each step narrows authority. There is no safe API to widen back.
148148

149-
## 7) Opaque, copy, affine, linear
149+
To make attenuation one-way at compile time, any method that returns a capability must take `self` by value. Methods that take `&self` cannot return capabilities.
150+
151+
Example of what is rejected (and why):
152+
153+
```cap
154+
capability struct Dir
155+
capability struct FileRead
156+
157+
impl Dir {
158+
pub fn open(self: &Dir, name: string) -> FileRead {
159+
let file = self.open_read(name)
160+
return file
161+
}
162+
}
163+
```
164+
165+
Why this is rejected:
166+
167+
- `Dir` can read many files (more power).
168+
- `FileRead` can read one file (less power).
169+
- The bad example lets you keep the more powerful `Dir` and also get a `FileRead`.
170+
- We want “one-way” attenuation: when you make something less powerful, you give up the more powerful one.
171+
172+
So methods that return capabilities must take `self` by value, which consumes the old capability.
173+
174+
## 7) Capability, opaque, copy, affine, linear
175+
176+
`capability struct` is the explicit “this is an authority token” marker. Capability types are always opaque (no public fields, no user construction) and default to affine unless marked `copy` or `linear`. This exists so the capability surface is obvious in code and the compiler can enforce one‑way attenuation (methods returning capabilities must take `self` by value).
150177

151178
Structs can declare their kind:
152179

153180
```cap
154-
opaque struct Token // affine by default (move-only)
155-
copy opaque struct RootCap // unrestricted (copyable)
156-
linear opaque struct FileRead // must be consumed
181+
capability struct Token // affine by default (move-only)
182+
copy capability struct RootCap // unrestricted (copyable)
183+
linear capability struct FileRead // must be consumed
157184
```
158185

159186
Kinds:
160187

161188
- **Unrestricted** (copy): can be reused freely.
162-
- **Affine** (default for opaque): move-only, dropping is OK.
189+
- **Affine** (default for capability/opaque): move-only, dropping is OK.
163190
- **Linear**: move-only and must be consumed on all paths.
164191

192+
Use `capability struct` for authority-bearing tokens. Use `opaque struct` for unforgeable data types that aren’t capabilities.
193+
194+
In the current stdlib:
195+
196+
- `copy capability`: `RootCap`, `Console`, `Args`
197+
- `copy opaque`: `Alloc`, `Buffer`, `Slice`, `MutSlice`, `VecU8`, `VecI32`, `VecString`
198+
- `capability` (affine): `ReadFS`, `Filesystem`, `Dir`, `Stdin`
199+
- `linear capability`: `FileRead`
200+
165201
## 8) Moves and use-after-move
166202

167203
```cap
168204
module moves
169205
170-
opaque struct Token
206+
capability struct Token
171207
172208
pub fn main() -> i32 {
173209
let t = Token{}
@@ -184,7 +220,7 @@ Affine and linear values cannot be used after move. If you move in one branch, i
184220
```cap
185221
module linear
186222
187-
linear opaque struct Ticket
223+
linear capability struct Ticket
188224
189225
pub fn main() -> i32 {
190226
let t = Ticket{}
@@ -202,7 +238,7 @@ There is a small borrow feature for read-only access in function parameters and
202238
```cap
203239
module borrow
204240
205-
opaque struct Cap
241+
capability struct Cap
206242
207243
impl Cap {
208244
pub fn ping(self: &Cap) -> i32 { return 1 }

capc/src/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub struct StructDecl {
9999
pub is_opaque: bool,
100100
pub is_linear: bool,
101101
pub is_copy: bool,
102+
pub is_capability: bool,
102103
pub doc: Option<String>,
103104
pub span: Span,
104105
}

capc/src/lexer.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub enum TokenKind {
4949
Impl,
5050
#[token("opaque")]
5151
Opaque,
52+
#[token("capability")]
53+
Capability,
5254
#[token("copy")]
5355
Copy,
5456
#[token("linear")]

capc/src/parser.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ impl Parser {
117117
let mut is_linear = false;
118118
let mut is_copy = false;
119119
let mut is_opaque = false;
120+
let mut is_capability = false;
120121
loop {
121122
match self.peek_kind() {
122123
Some(TokenKind::Pub) => {
@@ -155,6 +156,15 @@ impl Parser {
155156
self.bump();
156157
is_opaque = true;
157158
}
159+
Some(TokenKind::Capability) => {
160+
if is_capability {
161+
return Err(self.error_current(
162+
"duplicate `capability` modifier".to_string(),
163+
));
164+
}
165+
self.bump();
166+
is_capability = true;
167+
}
158168
_ => break,
159169
}
160170
}
@@ -164,18 +174,19 @@ impl Parser {
164174
));
165175
}
166176
if self.peek_kind() == Some(TokenKind::Extern) {
167-
if is_opaque || is_linear || is_copy {
177+
if is_opaque || is_linear || is_copy || is_capability {
168178
return Err(self.error_current(
169-
"linear/copy/opaque applies only to struct declarations".to_string(),
179+
"linear/copy/opaque/capability applies only to struct declarations".to_string(),
170180
));
171181
}
172182
return Ok(Item::ExternFunction(self.parse_extern_function(is_pub, doc)?));
173183
}
174184
match self.peek_kind() {
175185
Some(TokenKind::Fn) => {
176-
if is_opaque || is_linear || is_copy {
186+
if is_opaque || is_linear || is_copy || is_capability {
177187
return Err(self.error_current(
178-
"linear/copy/opaque applies only to struct declarations".to_string(),
188+
"linear/copy/opaque/capability applies only to struct declarations"
189+
.to_string(),
179190
));
180191
}
181192
Ok(Item::Function(self.parse_function(is_pub, doc)?))
@@ -185,12 +196,14 @@ impl Parser {
185196
is_opaque,
186197
is_linear,
187198
is_copy,
199+
is_capability,
188200
doc,
189201
)?)),
190202
Some(TokenKind::Enum) => {
191-
if is_opaque || is_linear || is_copy {
203+
if is_opaque || is_linear || is_copy || is_capability {
192204
return Err(self.error_current(
193-
"linear/copy/opaque applies only to struct declarations".to_string(),
205+
"linear/copy/opaque/capability applies only to struct declarations"
206+
.to_string(),
194207
));
195208
}
196209
Ok(Item::Enum(self.parse_enum(is_pub, doc)?))
@@ -201,9 +214,10 @@ impl Parser {
201214
"impl blocks cannot be marked pub".to_string(),
202215
));
203216
}
204-
if is_opaque || is_linear || is_copy {
217+
if is_opaque || is_linear || is_copy || is_capability {
205218
return Err(self.error_current(
206-
"linear/copy/opaque applies only to struct declarations".to_string(),
219+
"linear/copy/opaque/capability applies only to struct declarations"
220+
.to_string(),
207221
));
208222
}
209223
Ok(Item::Impl(self.parse_impl_block(doc)?))
@@ -330,6 +344,7 @@ impl Parser {
330344
is_opaque: bool,
331345
is_linear: bool,
332346
is_copy: bool,
347+
is_capability: bool,
333348
doc: Option<String>,
334349
) -> Result<StructDecl, ParseError> {
335350
let start = self.expect(TokenKind::Struct)?.span.start;
@@ -355,10 +370,10 @@ impl Parser {
355370
}
356371
}
357372
let end = self.expect(TokenKind::RBrace)?.span.end;
358-
if is_opaque && !fields.is_empty() {
373+
if (is_opaque || is_capability) && !fields.is_empty() {
359374
return Err(self.error_at(
360375
Span::new(start, end),
361-
"opaque struct cannot declare fields".to_string(),
376+
"opaque/capability struct cannot declare fields".to_string(),
362377
));
363378
}
364379
end
@@ -372,6 +387,7 @@ impl Parser {
372387
is_opaque,
373388
is_linear,
374389
is_copy,
390+
is_capability,
375391
doc,
376392
span: Span::new(start, end),
377393
})

capc/src/typeck/check.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,7 @@ pub(super) fn check_expr(
15941594
if info.is_opaque && info.module != module_name {
15951595
return Err(TypeError::new(
15961596
format!(
1597-
"cannot access fields of opaque type `{struct_name}` outside module `{}`",
1597+
"cannot access fields of opaque/capability type `{struct_name}` outside module `{}`",
15981598
info.module
15991599
),
16001600
field_access.span,
@@ -1976,7 +1976,7 @@ fn check_struct_literal(
19761976
if info.is_opaque && info.module != module_name {
19771977
return Err(TypeError::new(
19781978
format!(
1979-
"cannot construct opaque type `{}` outside module `{}`",
1979+
"cannot construct opaque/capability type `{}` outside module `{}`",
19801980
key, info.module
19811981
),
19821982
lit.span,

capc/src/typeck/collect.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub(super) fn collect_functions(
3737
entry_name: &str,
3838
stdlib: &StdlibIndex,
3939
struct_map: &HashMap<String, StructInfo>,
40+
enum_map: &HashMap<String, EnumInfo>,
4041
) -> Result<HashMap<String, FunctionSig>, TypeError> {
4142
let mut functions = HashMap::new();
4243
for module in modules {
@@ -118,6 +119,7 @@ pub(super) fn collect_functions(
118119
&local_use,
119120
stdlib,
120121
struct_map,
122+
enum_map,
121123
)?;
122124
for method in methods {
123125
if let Some((impl_ty, method_name)) = method.name.item.split_once("__") {
@@ -195,14 +197,15 @@ pub(super) fn collect_structs(
195197
TypeKind::Linear
196198
} else if decl.is_copy {
197199
TypeKind::Unrestricted
198-
} else if decl.is_opaque {
200+
} else if decl.is_opaque || decl.is_capability {
199201
TypeKind::Affine
200202
} else {
201203
TypeKind::Unrestricted
202204
};
203205
let info = StructInfo {
204206
fields,
205-
is_opaque: decl.is_opaque,
207+
is_opaque: decl.is_opaque || decl.is_capability,
208+
is_capability: decl.is_capability,
206209
kind: declared_kind,
207210
module: module_name.clone(),
208211
};

capc/src/typeck/lower.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ pub(super) fn lower_module(
121121
use_map,
122122
stdlib,
123123
structs,
124+
enums,
124125
)?;
125126
for method in methods {
126127
let hir_func = lower_function(&method, &mut ctx)?;
@@ -175,7 +176,7 @@ pub(super) fn lower_module(
175176
hir_structs.push(HirStruct {
176177
name: decl.name.item.clone(),
177178
fields: fields?,
178-
is_opaque: decl.is_opaque,
179+
is_opaque: decl.is_opaque || decl.is_capability,
179180
});
180181
}
181182
}
@@ -826,7 +827,7 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result<HirExpr
826827
if info.is_opaque && info.module != ctx.module_name {
827828
return Err(TypeError::new(
828829
format!(
829-
"cannot construct opaque type `{}` outside module `{}`",
830+
"cannot construct opaque/capability type `{}` outside module `{}`",
830831
key, info.module
831832
),
832833
lit.span,
@@ -1042,6 +1043,7 @@ mod tests {
10421043
StructInfo {
10431044
fields: HashMap::new(),
10441045
is_opaque: false,
1046+
is_capability: false,
10451047
kind: TypeKind::Unrestricted,
10461048
module: "foo".to_string(),
10471049
},

0 commit comments

Comments
 (0)