Skip to content

Commit

Permalink
Allow creating uninitialized C structs
Browse files Browse the repository at this point in the history
This adds support for creating uninitialized C structures by
leaving out _all_ the field assignments when creating an instance. This
makes dealing with large structs that are initialized by a separate
function less painful, as one doesn't have to explicitly assign all
fields to some value (along with the necessary type casts).

Changelog: added
  • Loading branch information
yorickpeterse committed Sep 3, 2024
1 parent 73f31dc commit df3b4fa
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 174 deletions.
33 changes: 22 additions & 11 deletions compiler/src/type_check/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3849,7 +3849,8 @@ impl<'a> CheckMethodBody<'a> {
);
}

let require_send = class.kind(self.db()).is_async();
let kind = class.kind(self.db());
let require_send = kind.is_async();
let ins = ClassInstance::empty(self.db_mut(), class);
let mut assigned = HashSet::new();
let mut fields = Vec::new();
Expand Down Expand Up @@ -3984,17 +3985,27 @@ impl<'a> CheckMethodBody<'a> {
fields.push((field, expected));
}

for field in class.field_names(self.db()) {
if assigned.contains(&field) {
continue;
}
// For extern classes we allow either all fields to be specified, or all
// fields to be left out. The latter is useful when dealing with C
// structures that start on the stack as uninitialized data and are
// initialized using a dedicated function.
//
// If an extern class has one or more fields specifid, then we require
// _all_ fields to be specified, as leaving out fields in this case is
// likely the result of a mistake.
if !kind.is_extern() || !assigned.is_empty() {
for field in class.field_names(self.db()) {
if assigned.contains(&field) {
continue;
}

self.state.diagnostics.error(
DiagnosticId::MissingField,
format!("the field '{}' must be assigned a value", field),
self.file(),
node.location.clone(),
);
self.state.diagnostics.error(
DiagnosticId::MissingField,
format!("the field '{}' must be assigned a value", field),
self.file(),
node.location.clone(),
);
}
}

let resolved_type = TypeRef::Owned(TypeId::ClassInstance(ins));
Expand Down
19 changes: 19 additions & 0 deletions docs/source/getting-started/ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,25 @@ class async Main {
}
```

When creating an instance of a struct we can leave out all the fields. This
reserves the necessary stack space but doesn't initialize it. This is useful
when dealing with large structs that are initialized by a C function:

```inko
class extern Timespec {
let @tv_sec: Int64
let @tv_nsec: Int64
}
class async Main {
fn async main {
let spec = Timespec()
hypothetical_initialization_function_that_sets_the_fields(mut spec)
}
}
```

Structures are allocated on the stack and are value types, meaning a move
results in a copy (unless this is optimised away). Reading and writing of
structure fields uses the same syntax as regular Inko classes:
Expand Down
19 changes: 19 additions & 0 deletions std/fixtures/diagnostics/class_extern_without_fields.inko
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class extern Foo {
let @a: Int32
let @b: Int32
let @c: Int32
}

fn example1 {
let foo = Foo()
}

fn example2 {
let foo = Foo(a: 0 as Int32, b: 1 as Int32, c: 2 as Int32)
}

fn example3 {
let foo = Foo(a: 0 as Int32, b: 1 as Int32)
}

# class_extern_without_fields.inko:16:13 error(missing-field): the field 'c' must be assigned a value
62 changes: 2 additions & 60 deletions std/src/std/sys/freebsd/fs.inko
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,7 @@ fn stat_to_metadata(buf: Pointer[libc.StatBuf]) -> Metadata {
}

fn file_metadata(fd: Int32) -> Result[Metadata, Error] {
let buf = libc.StatBuf(
st_dev: 0 as UInt64,
st_ino: 0 as UInt64,
st_nlink: 0 as UInt64,
st_mode: 0 as UInt16,
__pad0: 0 as Int16,
st_uid: 0 as UInt32,
st_gid: 0 as UInt32,
__pad1: 0 as Int32,
st_rdev: 0 as UInt64,
st_atim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_mtim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_ctim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_birthtim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_size: 0 as Int64,
st_blocks: 0 as Int64,
st_blksize: 0 as Int32,
st_flags: 0 as UInt32,
st_gen: 0 as UInt64,
st_spare0: 0 as Int64,
st_spare1: 0 as Int64,
st_spare2: 0 as Int64,
st_spare3: 0 as Int64,
st_spare4: 0 as Int64,
st_spare5: 0 as Int64,
st_spare6: 0 as Int64,
st_spare7: 0 as Int64,
st_spare8: 0 as Int64,
st_spare9: 0 as Int64,
)
let buf = libc.StatBuf()

start_blocking

Expand All @@ -70,36 +41,7 @@ fn file_metadata(fd: Int32) -> Result[Metadata, Error] {
}

fn path_metadata(path: String) -> Result[Metadata, Error] {
let buf = libc.StatBuf(
st_dev: 0 as UInt64,
st_ino: 0 as UInt64,
st_nlink: 0 as UInt64,
st_mode: 0 as UInt16,
__pad0: 0 as Int16,
st_uid: 0 as UInt32,
st_gid: 0 as UInt32,
__pad1: 0 as Int32,
st_rdev: 0 as UInt64,
st_atim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_mtim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_ctim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_birthtim: libc.Timespec(tv_sec: 0 as Int64, tv_nsec: 0 as Int64),
st_size: 0 as Int64,
st_blocks: 0 as Int64,
st_blksize: 0 as Int32,
st_flags: 0 as UInt32,
st_gen: 0 as UInt64,
st_spare0: 0 as Int64,
st_spare1: 0 as Int64,
st_spare2: 0 as Int64,
st_spare3: 0 as Int64,
st_spare4: 0 as Int64,
st_spare5: 0 as Int64,
st_spare6: 0 as Int64,
st_spare7: 0 as Int64,
st_spare8: 0 as Int64,
st_spare9: 0 as Int64,
)
let buf = libc.StatBuf()

start_blocking

Expand Down
54 changes: 1 addition & 53 deletions std/src/std/sys/linux/fs.inko
Original file line number Diff line number Diff line change
Expand Up @@ -12,59 +12,7 @@ fn statx_time(time: Pointer[libc.StatxTimestamp]) -> Time {
}

fn statx(fd: Int32, path: String) -> Result[Metadata, Error] {
let buf = libc.StatxBuf(
stx_mask: 0 as UInt32,
stx_blksize: 0 as UInt32,
stx_attributes: 0 as UInt64,
stx_nlink: 0 as UInt32,
stx_uid: 0 as UInt32,
stx_gid: 0 as UInt32,
stx_mode: 0 as UInt16,
__pad0: 0 as UInt16,
stx_ino: 0 as UInt64,
stx_size: 0 as UInt64,
stx_blocks: 0 as UInt64,
stx_attributes_mask: 0 as UInt64,
stx_atime: libc.StatxTimestamp(
tv_sec: 0 as Int64,
tv_nsec: 0 as UInt32,
__pad0: 0 as Int32,
),
stx_btime: libc.StatxTimestamp(
tv_sec: 0 as Int64,
tv_nsec: 0 as UInt32,
__pad0: 0 as Int32,
),
stx_ctime: libc.StatxTimestamp(
tv_sec: 0 as Int64,
tv_nsec: 0 as UInt32,
__pad0: 0 as Int32,
),
stx_mtime: libc.StatxTimestamp(
tv_sec: 0 as Int64,
tv_nsec: 0 as UInt32,
__pad0: 0 as Int32,
),
stx_rdev_major: 0 as UInt32,
stx_rdev_minor: 0 as UInt32,
stx_dev_major: 0 as UInt32,
stx_dev_minor: 0 as UInt32,
stx_mnt_id: 0 as UInt64,
stx_dio_mem_align: 0 as UInt32,
stx_dio_offset_align: 0 as UInt32,
__pad1: 0 as UInt64,
__pad2: 0 as UInt64,
__pad3: 0 as UInt64,
__pad4: 0 as UInt64,
__pad5: 0 as UInt64,
__pad6: 0 as UInt64,
__pad7: 0 as UInt64,
__pad8: 0 as UInt64,
__pad9: 0 as UInt64,
__pad10: 0 as UInt64,
__pad11: 0 as UInt64,
__pad12: 0 as UInt64,
)
let buf = libc.StatxBuf()

start_blocking

Expand Down
52 changes: 2 additions & 50 deletions std/src/std/sys/mac/fs.inko
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,7 @@ fn stat_to_metadata(buf: Pointer[libc.StatBuf]) -> Metadata {
}

fn file_metadata(fd: Int32) -> Result[Metadata, Error] {
let buf = libc.StatBuf(
st_dev: 0 as Int32,
st_mode: 0 as UInt16,
st_nlink: 0 as UInt16,
st_ino: 0 as UInt64,
st_uid: 0 as UInt32,
st_gid: 0 as UInt32,
st_rdev: 0 as Int32,
st_atime: 0 as Int64,
st_atime_nsec: 0 as Int64,
st_mtime: 0 as Int64,
st_mtime_nsec: 0 as Int64,
st_ctime: 0 as Int64,
st_ctime_nsec: 0 as Int64,
st_birthtime: 0 as Int64,
st_birthtime_nsec: 0 as Int64,
st_size: 0 as Int64,
st_blocks: 0 as Int64,
st_blksize: 0 as Int32,
st_flags: 0 as UInt32,
st_gen: 0 as UInt32,
st_lspare: 0 as Int32,
st_qspare0: 0 as Int64,
st_qspare1: 0 as Int64,
)
let buf = libc.StatBuf()

start_blocking

Expand All @@ -66,31 +42,7 @@ fn file_metadata(fd: Int32) -> Result[Metadata, Error] {
}

fn path_metadata(path: String) -> Result[Metadata, Error] {
let buf = libc.StatBuf(
st_dev: 0 as Int32,
st_mode: 0 as UInt16,
st_nlink: 0 as UInt16,
st_ino: 0 as UInt64,
st_uid: 0 as UInt32,
st_gid: 0 as UInt32,
st_rdev: 0 as Int32,
st_atime: 0 as Int64,
st_atime_nsec: 0 as Int64,
st_mtime: 0 as Int64,
st_mtime_nsec: 0 as Int64,
st_ctime: 0 as Int64,
st_ctime_nsec: 0 as Int64,
st_birthtime: 0 as Int64,
st_birthtime_nsec: 0 as Int64,
st_size: 0 as Int64,
st_blocks: 0 as Int64,
st_blksize: 0 as Int32,
st_flags: 0 as UInt32,
st_gen: 0 as UInt32,
st_lspare: 0 as Int32,
st_qspare0: 0 as Int64,
st_qspare1: 0 as Int64,
)
let buf = libc.StatBuf()

start_blocking

Expand Down

0 comments on commit df3b4fa

Please sign in to comment.