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
7 changes: 7 additions & 0 deletions docs/haven.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ top_decl ::= import
| foreign_block
| fn_decl
| tydecl
| extend_decl
| global_decl

import ::= 'import' STRING ';'
Expand Down Expand Up @@ -39,6 +40,12 @@ type_body ::= type
| struct_decl
| enum_decl

extend_decl ::= 'extend' IDENT 'with' '{' extend_item* '}'
extend_item ::= 'construct' block
| 'construct' '(' param_list? ')' block
| 'destruct' block
param_list ::= param (',' param)*

struct_decl ::= 'struct' '{' struct_field+ '}'
struct_field ::= type IDENT ';'

Expand Down
54 changes: 54 additions & 0 deletions docs/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,34 @@ fn example() -> i32 {
}
```

`box` also supports type-directed construction:

```
type Buffer = struct {
i32 len;
i32 cap;
};

extend Buffer with {
construct(i32 cap) {
self->len = 0;
self->cap = cap;
}
}

fn example() -> i32 {
let mut boxed = box Buffer(64);
let value = unbox boxed;
boxed = nil;
value.cap
}
```

`box T` allocates storage for `T`, recursively default-initializes its members, and then runs a zero-argument
constructor if `T` defines one. `box T(args...)` performs the same recursive default initialization, then calls
`construct` with the supplied arguments. This means member pointers are `nil` and inline subobjects are already in a
known state before `construct` runs.

Box types are written much like pointers, but using a caret (`^`) instead
of an asterisk (`*`):

Expand Down Expand Up @@ -319,6 +347,32 @@ declarations will also be automatically marked `pub` and `impure`, simplifying t

Type declarations (`type X = ...`) may only appear at the file scope.

### Type Extensions

Type extensions attach behavior to an existing type without changing its layout.

```
extend Buffer with {
construct(i32 cap) {
self->len = 0;
self->cap = cap;
}

destruct {
self->len = 0;
}
}
```

In the current model, `extend` is behavioral only:

- `construct(...) { ... }` defines an optional constructor hook.
- `destruct { ... }` defines an optional destructor hook.
- `self` is provided implicitly inside both hooks and is pointer-like, so fields are accessed with `self->field`.

Constructors run automatically after recursive default-initialization. Destructors run automatically when the final
boxed reference is released. `extend` does not add fields or change ABI layout.

### Variable Declarations

#### File Scope
Expand Down
36 changes: 36 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ top_decl ::= import
| foreign_block
| fn_decl
| tydecl
| extend_decl
| global_decl
```

Expand All @@ -47,6 +48,17 @@ referenced by:

* top_decl

**extend_decl:**

```
extend_decl
::= 'extend' IDENT 'with' '{' extend_item* '}'
```

referenced by:

* top_decl

**cimport:**

![cimport](diagram/cimport.svg)
Expand Down Expand Up @@ -259,6 +271,30 @@ referenced by:

* tydecl

**extend_item:**

```
extend_item
::= 'construct' block
| 'construct' '(' param_list? ')' block
| 'destruct' block
```

referenced by:

* extend_decl

**param_list:**

```
param_list
::= param ( ',' param )*
```

referenced by:

* extend_item

**struct_decl:**

![struct_decl](diagram/struct_decl.svg)
Expand Down
22 changes: 22 additions & 0 deletions examples/construct_box.hv
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
type Buffer = struct {
i32 len;
i32 cap;
};

extend Buffer with {
construct(i32 cap) {
self->len = 0;
self->cap = cap;
}

destruct {
self->len = 0;
}
}

pub impure fn main() -> i32 {
let mut boxed = box Buffer(64);
let value = unbox boxed;
boxed = nil;
value.cap
}
4 changes: 3 additions & 1 deletion src/bin/lsp/document_structure.ml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ let folding_ranges (parsed : Cst.parsed_program) =
let structural =
Locate.nodes_matching
(function
| Locate.Block _ | StructDecl _ | EnumDecl _ | Foreign _ -> true
| Locate.Block _ | StructDecl _ | EnumDecl _ | Foreign _ | TypeExtend _ -> true
| _ -> false)
parsed.program
|> List.filter_map (fun node ->
Expand All @@ -78,6 +78,8 @@ let folding_ranges (parsed : Cst.parsed_program) =
folding_range ~kind:FoldingRangeKind.Region enum_decl.loc
| Foreign foreign ->
folding_range ~kind:FoldingRangeKind.Region foreign.loc
| TypeExtend ext ->
folding_range ~kind:FoldingRangeKind.Region ext.loc
| _ ->
None)
in
Expand Down
30 changes: 30 additions & 0 deletions src/bin/lsp/document_symbols.ml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ let variant_symbol (variant : Cst.enum_variant) =
~selection_range:(Lsp_helpers.loc_to_range variant.value.name.loc)
?detail:(variant_detail variant) ()

let extend_item_symbol (item : Cst.extend_item) =
match item.value with
| Cst.ExtendConstruct construct ->
make_symbol ~kind:SymbolKind.Constructor ~name:"construct"
~range:(Lsp_helpers.loc_to_range item.loc)
~selection_range:(Lsp_helpers.loc_to_range construct.loc)
?detail:
(Some
(Printf.sprintf "(%s)"
(String.concat ", "
(List.map
(fun (param : Cst.param) -> type_text param.value.ty)
construct.value.params))))
()
| Cst.ExtendDestruct block ->
make_symbol ~kind:SymbolKind.Method ~name:"destruct"
~range:(Lsp_helpers.loc_to_range item.loc)
~selection_range:(Lsp_helpers.loc_to_range block.loc) ()

let extend_symbol (ext : Cst.type_extend) =
make_symbol
~kind:SymbolKind.Object
~name:(Printf.sprintf "extend %s" ext.value.target.value)
~range:(Lsp_helpers.loc_to_range ext.loc)
~selection_range:(Lsp_helpers.loc_to_range ext.value.target.loc)
~children:(List.map extend_item_symbol ext.value.items)
()

let function_symbol (fn : Cst.function_decl) =
make_symbol ~kind:SymbolKind.Function ~name:fn.value.name.value
~range:(Lsp_helpers.loc_to_range fn.loc)
Expand Down Expand Up @@ -102,6 +130,8 @@ let symbols_for_program (parsed : Cst.parsed_program) =
[ variable_symbol var_decl ]
| Cst.TDecl type_decl ->
[ type_symbol type_decl ]
| Cst.Extend ext ->
[ extend_symbol ext ]
| Cst.Foreign foreign ->
List.map function_symbol foreign.value.decls
| Cst.Import _ | Cst.CImport _ ->
Expand Down
4 changes: 4 additions & 0 deletions src/bin/lsp/inlay_hints.ml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ let rec walk_expression typing type_env query_range acc (expr : Core.expression)
acc
| ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner ->
walk_expression typing type_env query_range acc inner
| BoxConstruct box ->
List.fold_left
(walk_expression typing type_env query_range)
acc box.value.args
| Initializer init ->
List.fold_left
(walk_expression typing type_env query_range)
Expand Down
6 changes: 6 additions & 0 deletions src/bin/lsp/symbol_resolution.ml
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,9 @@ and walk_expression state env (expr : Core.expression) =
| Nil -> ()
| ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner ->
walk_expression state env inner
| BoxConstruct box ->
walk_type state box.value.ty;
List.iter (walk_expression state env) box.value.args
| Initializer init ->
List.iter (walk_expression state env) init.value.exprs
| As cast ->
Expand Down Expand Up @@ -774,6 +777,9 @@ let rec highlight_expression state env (expr : Core.expression) =
| Nil -> ()
| ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner ->
highlight_expression state env inner
| BoxConstruct box ->
highlight_type state box.value.ty;
List.iter (highlight_expression state env) box.value.args
| Initializer init ->
List.iter (highlight_expression state env) init.value.exprs
| As cast ->
Expand Down
9 changes: 9 additions & 0 deletions src/lib/ast/analysis_asserts.ml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ module Assert = struct
| Core.Match _ -> "match { ... }"
| Core.BoxExpr inner -> "box " ^ render_expression ~ctx_prec:11 inner
| Core.BoxType _ -> "box <type>"
| Core.BoxConstruct box ->
Printf.sprintf "box <type>(%s)"
(String.concat ", " (List.map render_expression box.value.args))
| Core.Unbox inner -> "unbox " ^ render_expression ~ctx_prec:11 inner
| Core.Ref inner -> "ref " ^ render_expression ~ctx_prec:11 inner
| Core.Load inner -> "load " ^ render_expression ~ctx_prec:11 inner
Expand Down Expand Up @@ -197,6 +200,12 @@ module Assert = struct
Printf.sprintf "%s := %s"
(render_specialized_expression state write.value.target)
(render_specialized_expression state write.value.value)
| Core.BoxConstruct box ->
Printf.sprintf "box <type>(%s)"
(String.concat ", "
(List.map
(fun expr -> render_specialized_expression state expr)
box.value.args))
| (Core.Literal _ | Core.Identifier _ | Core.Block _ | Core.Initializer _ | Core.Match _
| Core.BoxType _ | Core.SizeType _ | Core.Nil) ->
render_expression ~ctx_prec expr
Expand Down
13 changes: 11 additions & 2 deletions src/lib/ast/analysis_cfold.ml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ module ConstantFold = struct
};
}
| Core.BoxExpr inner -> Core.BoxExpr (fold_expression inner)
| Core.BoxConstruct box ->
Core.BoxConstruct
{
box with
value = { box.value with args = List.map fold_expression box.value.args };
}
| Core.Unbox inner -> Core.Unbox (fold_expression inner)
| Core.Ref inner -> Core.Ref (fold_expression inner)
| Core.Load inner -> Core.Load (fold_expression inner)
Expand Down Expand Up @@ -279,11 +285,14 @@ module ConstantFold = struct
| Core.Assign write -> Core.Assign (fold_write write)
| Core.Mutate write -> Core.Mutate (fold_write write)
| Core.Literal literal -> Core.Literal (fold_literal literal)
| (Core.Identifier _ | Core.SizeType _ | Core.Nil | Core.BoxType _) as value -> value
| (Core.Identifier _ | Core.SizeType _ | Core.Nil | Core.BoxType _) as value ->
value
in
let expr = { expr with value } in
match expr.value with
| Core.Literal _ | Core.Identifier _ | Core.Nil | Core.SizeType _ | Core.BoxType _ -> expr
| Core.Literal _ | Core.Identifier _ | Core.Nil | Core.SizeType _ | Core.BoxType _
| Core.BoxConstruct _ ->
expr
| Core.Block block when block.value.statements = [] -> (
match block.value.result with Some result -> result | None -> expr)
| Core.ToBool inner -> (
Expand Down
8 changes: 7 additions & 1 deletion src/lib/ast/analysis_cleanup.ml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ module Cleanup = struct
};
}
| Core.BoxExpr inner -> Core.BoxExpr (clean_expression typed inner)
| Core.BoxConstruct box ->
Core.BoxConstruct
{
box with
value = { box.value with args = List.map (clean_expression typed) box.value.args };
}
| Core.Unbox inner -> Core.Unbox (clean_expression typed inner)
| Core.Ref inner -> Core.Ref (clean_expression typed inner)
| Core.Load inner -> Core.Load (clean_expression typed inner)
Expand Down Expand Up @@ -123,7 +129,7 @@ module Cleanup = struct
| Core.Assign write -> clean_write_like typed (fun write -> Core.Assign write) write
| Core.Mutate write -> clean_write_like typed (fun write -> Core.Mutate write) write
| (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.BoxType _) as
value ->
value ->
value
in
{ expr with value = cleaned_value }
Expand Down
4 changes: 3 additions & 1 deletion src/lib/ast/analysis_ownership.ml
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ module Ownership = struct
else
match expr.value with
| Core.Nil -> ()
| Core.BoxExpr _ | Core.BoxType _ -> ()
| Core.BoxExpr _ | Core.BoxType _ | Core.BoxConstruct _ -> ()
| Core.Call call -> emit_retains_for_expected_enum_call state reason expected call
| Core.As cast -> emit_retains_for_transfer state reason expected cast.value.inner
| Core.Block block ->
Expand Down Expand Up @@ -264,6 +264,8 @@ module Ownership = struct
let rec visit_expression state scopes (expr : Core.expression) =
match expr.value with
| Core.Identifier _ | Core.Nil | Core.SizeType _ | Core.BoxType _ -> ()
| Core.BoxConstruct box ->
List.iter (visit_expression state scopes) box.value.args
| Core.Literal literal -> (
match literal.value with
| Core.Vector vec ->
Expand Down
2 changes: 2 additions & 0 deletions src/lib/ast/analysis_purity.ml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ module Purity = struct
let visit = visit_expression state current in
match expr.value with
| Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.BoxType _ | Core.Nil -> ()
| Core.BoxConstruct box ->
List.iter (visit env) box.value.args
| Core.ToBool inner
| Core.SizeExpr inner
| Core.BoxExpr inner
Expand Down
19 changes: 11 additions & 8 deletions src/lib/ast/analysis_semantic.ml
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,8 @@ module Semantic = struct
| _ -> ())
| Core.BoxExpr inner ->
check_expression state env loop_depth inner
| Core.BoxConstruct box ->
List.iter (check_expression state env loop_depth) box.value.args
| Core.Unbox inner ->
check_expression state env loop_depth inner;
(match expr_resolved_type state inner with
Expand Down Expand Up @@ -751,14 +753,15 @@ module Semantic = struct
if not (is_lvalue write.value.target) then
add_diagnostic state Error expr.loc
"assignment target must be assignable";
Option.iter
(fun name ->
match lookup env name with
| Some binding when not binding.is_mutable ->
add_diagnostic state Error expr.loc
(Printf.sprintf "assignment to immutable binding %s" name)
| _ -> ())
(root_identifier_name write.value.target);
if assignment_requires_mutable_root write.value.target then
Option.iter
(fun name ->
match lookup env name with
| Some binding when not binding.is_mutable ->
add_diagnostic state Error expr.loc
(Printf.sprintf "assignment to immutable binding %s" name)
| _ -> ())
(root_identifier_name write.value.target);
(match
( expr_annotation state write.value.target,
expr_annotation state write.value.value )
Expand Down
Loading
Loading