merge master

This commit is contained in:
kbkpbot 2025-09-05 21:34:13 +08:00
commit a130a758e2
28 changed files with 254 additions and 69 deletions

1
.gitattributes vendored
View file

@ -8,3 +8,4 @@ v.mod linguist-language=V
.vdocignore linguist-language=ignore
Dockerfile.* linguist-language=Dockerfile
vlib/v/tests/runes.txt binary

View file

@ -441,7 +441,7 @@ fn run_repl(workdir string, vrepl_prefix string) int {
prompt = '... '
}
oline := r.get_one_line(prompt) or { break }
line := oline.trim_space()
line := oline.all_before('//').trim_space()
if line == '' {
continue
}

View file

@ -124,9 +124,10 @@ pub:
typ int // the internal TypeID of the field f,
unaliased_typ int // if f's type was an alias of int, this will be TypeID(int)
attrs []string // the attributes of the field f
is_pub bool // f is in a `pub:` section
is_mut bool // f is in a `mut:` section
attrs []string // the attributes of the field f
is_pub bool // f is in a `pub:` section
is_mut bool // f is in a `mut:` section
is_embed bool // f is a embedded struct
is_shared bool // `f shared Abc`
is_atomic bool // `f atomic int` , TODO

View file

@ -0,0 +1,40 @@
module builtin
// input_rune returns a single rune from the standart input (an unicode codepoint).
// It expects, that the input is utf8 encoded.
// It will return `none` on EOF.
pub fn input_rune() ?rune {
x := input_character()
if x <= 0 {
return none
}
char_len := utf8_char_len(u8(x))
if char_len == 1 {
return x
}
mut b := u8(x)
b = b << char_len
mut res := rune(b)
mut shift := 6 - char_len
for i := 1; i < char_len; i++ {
c := rune(input_character())
res = rune(res) << shift
res |= c & 63 // 0x3f
shift = 6
}
return res
}
// InputRuneIterator is an iterator over the input runes.
pub struct InputRuneIterator {}
// next returns the next rune from the input stream.
pub fn (mut self InputRuneIterator) next() ?rune {
return input_rune()
}
// input_rune_iterator returns an iterator to allow for `for i, r in input_rune_iterator() {`.
// When the input stream is closed, the loop will break.
pub fn input_rune_iterator() InputRuneIterator {
return InputRuneIterator{}
}

View file

@ -0,0 +1,51 @@
// vtest build: !windows
import os
import time
fn test_input_rune_iterator_with_unicode_input() {
mut p := os.new_process(@VEXE)
p.set_args(['-e', 'for i, r in input_rune_iterator() { println("> i: \${i:04} | r: `\${r}`") }'])
p.set_redirect_stdio()
p.run()
spawn fn [mut p] () {
time.sleep(10 * time.millisecond)
dump(p.pid)
p.stdin_write('Проба Abc 🌍 123')
time.sleep(10 * time.millisecond)
p.stdin_write('\0x00') // 0 should break the input stream
time.sleep(10 * time.millisecond)
eprintln('>>> done')
}()
mut olines := []string{}
for p.is_alive() {
if oline := p.pipe_read(.stdout) {
olines << oline
}
time.sleep(1 * time.millisecond)
}
p.close()
p.wait()
assert p.code == 0
eprintln('done')
solines := olines.join('\n').trim_space().replace('\r', '')
eprintln('solines.len: ${solines.len} | solines: ${solines}')
assert solines.len > 100
assert solines == '> i: 0000 | r: `П`
> i: 0001 | r: `р`
> i: 0002 | r: `о`
> i: 0003 | r: `б`
> i: 0004 | r: `а`
> i: 0005 | r: ` `
> i: 0006 | r: `A`
> i: 0007 | r: `b`
> i: 0008 | r: `c`
> i: 0009 | r: ` `
> i: 0010 | r: ``
> i: 0011 | r: ``
> i: 0012 | r: ` `
> i: 0013 | r: `🌍`
> i: 0014 | r: ` `
> i: 0015 | r: `1`
> i: 0016 | r: `2`
> i: 0017 | r: `3`'
}

View file

@ -3,7 +3,7 @@ module builtin
// Note: this file will be removed soon
// byteptr.vbytes() - makes a V []u8 structure from a C style memory buffer. Note: the data is reused, NOT copied!
@[unsafe]
@[reused; unsafe]
pub fn (data byteptr) vbytes(len int) []u8 {
return unsafe { voidptr(data).vbytes(len) }
}
@ -11,7 +11,7 @@ pub fn (data byteptr) vbytes(len int) []u8 {
// vstring converts a C style string to a V string. Note: the string data is reused, NOT copied.
// strings returned from this function will be normal V strings beside that (i.e. they would be
// freed by V's -autofree mechanism, when they are no longer used).
@[unsafe]
@[reused; unsafe]
pub fn (bp byteptr) vstring() string {
return string{
str: bp
@ -21,7 +21,7 @@ pub fn (bp byteptr) vstring() string {
// vstring_with_len converts a C style string to a V string.
// Note: the string data is reused, NOT copied.
@[unsafe]
@[reused; unsafe]
pub fn (bp byteptr) vstring_with_len(len int) string {
return string{
str: bp
@ -32,7 +32,7 @@ pub fn (bp byteptr) vstring_with_len(len int) string {
// vstring converts C char* to V string.
// Note: the string data is reused, NOT copied.
@[unsafe]
@[reused; unsafe]
pub fn (cp charptr) vstring() string {
return string{
str: byteptr(cp)
@ -43,7 +43,7 @@ pub fn (cp charptr) vstring() string {
// vstring_with_len converts C char* to V string.
// Note: the string data is reused, NOT copied.
@[unsafe]
@[reused; unsafe]
pub fn (cp charptr) vstring_with_len(len int) string {
return string{
str: byteptr(cp)
@ -59,7 +59,7 @@ pub fn (cp charptr) vstring_with_len(len int) string {
// This is suitable for readonly strings, C string literals etc,
// that can be read by the V program, but that should not be
// managed by it, for example `os.args` is implemented using it.
@[unsafe]
@[reused; unsafe]
pub fn (bp byteptr) vstring_literal() string {
return string{
str: bp
@ -70,7 +70,7 @@ pub fn (bp byteptr) vstring_literal() string {
// vstring_with_len converts a C style string to a V string.
// Note: the string data is reused, NOT copied.
@[unsafe]
@[reused; unsafe]
pub fn (bp byteptr) vstring_literal_with_len(len int) string {
return string{
str: bp
@ -82,7 +82,7 @@ pub fn (bp byteptr) vstring_literal_with_len(len int) string {
// vstring_literal converts C char* to V string.
// See also vstring_literal defined on byteptr for more details.
// Note: the string data is reused, NOT copied.
@[unsafe]
@[reused; unsafe]
pub fn (cp charptr) vstring_literal() string {
return string{
str: byteptr(cp)
@ -94,7 +94,7 @@ pub fn (cp charptr) vstring_literal() string {
// vstring_literal_with_len converts C char* to V string.
// See also vstring_literal_with_len defined on byteptr.
// Note: the string data is reused, NOT copied.
@[unsafe]
@[reused; unsafe]
pub fn (cp charptr) vstring_literal_with_len(len int) string {
return string{
str: byteptr(cp)

View file

@ -393,7 +393,9 @@ fn test_orm() {
// Note: usually updated_time_mod.created != t, because t has
// its microseconds set, while the value retrieved from the DB
// has them zeroed, because the db field resolution is seconds.
assert modules.first().created.format_ss() == t.format_ss()
// Note: the database also stores the time in UTC, so the
// comparison must be done on the unix timestamp.
assert modules.first().created.unix() == t.unix()
users = sql db {
select from User where (name == 'Sam' && is_customer == true) || id == 1

View file

@ -5,6 +5,10 @@ import strings
#include <sys/stat.h> // #include <signal.h>
#include <errno.h>
$if macos {
#include <libproc.h>
}
$if freebsd || openbsd {
#include <sys/sysctl.h>
}
@ -715,7 +719,7 @@ pub fn executable() string {
}
$if macos {
pid := C.getpid()
ret := proc_pidpath(pid, &result[0], max_path_len)
ret := C.proc_pidpath(pid, &result[0], max_path_len)
if ret <= 0 {
eprintln('os.executable() failed at calling proc_pidpath with pid: ${pid} . proc_pidpath returned ${ret} ')
return executable_fallback()

View file

@ -3,13 +3,16 @@ module time
// operator `==` returns true if provided time is equal to time
@[inline]
pub fn (t1 Time) == (t2 Time) bool {
return t1.unix() == t2.unix() && t1.nanosecond == t2.nanosecond
return t1.is_local == t2.is_local && t1.local_unix() == t2.local_unix()
&& t1.nanosecond == t2.nanosecond
}
// operator `<` returns true if provided time is less than time
@[inline]
pub fn (t1 Time) < (t2 Time) bool {
return t1.unix() < t2.unix() || (t1.unix() == t2.unix() && t1.nanosecond < t2.nanosecond)
t1u := t1.unix()
t2u := t2.unix()
return t1u < t2u || (t1u == t2u && t1.nanosecond < t2.nanosecond)
}
// Time subtract using operator overloading.

View file

@ -1,14 +0,0 @@
// tests that use and test private functions
module time
// test the old behavior is same as new, the unix time should always be local time
fn test_new_is_same_as_old_for_all_platforms() {
t := C.time(0)
tm := C.localtime(&t)
old_time := convert_ctime(tm, 0)
new_time := now()
diff := new_time.unix - old_time.unix
// could in very rare cases be that the second changed between calls
dump(diff)
assert (diff >= -2 && diff <= 2) == true
}

View file

@ -106,6 +106,12 @@ pub fn (t Time) smonth() string {
// unix returns the UNIX time with second resolution.
@[inline]
pub fn (t Time) unix() i64 {
return time_with_unix(t.local_to_utc()).unix
}
// local_unix returns the UNIX local time with second resolution.
@[inline]
pub fn (t Time) local_unix() i64 {
return time_with_unix(t).unix
}
@ -135,14 +141,26 @@ pub fn (t Time) add(duration_in_nanosecond Duration) Time {
// ... so instead, handle the addition manually in parts ¯\_(ツ)_/¯
mut increased_time_nanosecond := i64(t.nanosecond) + duration_in_nanosecond.nanoseconds()
// increased_time_second
mut increased_time_second := t.unix() + (increased_time_nanosecond / second)
mut increased_time_second := t.local_unix() + (increased_time_nanosecond / second)
increased_time_nanosecond = increased_time_nanosecond % second
if increased_time_nanosecond < 0 {
increased_time_second--
increased_time_nanosecond += second
}
res := unix_nanosecond(increased_time_second, int(increased_time_nanosecond))
return if t.is_local { res.as_local() } else { res }
if t.is_local {
// we need to reset unix to 0, because we don't know the offset
// and we can't calculate it without it without causing infinite recursion
// so unfortunately we need to recalculate unix next time it is needed
return Time{
...res
is_local: true
unix: 0
}
}
return res
}
// add_seconds returns a new time struct with an added number of seconds.
@ -177,7 +195,7 @@ pub fn since(t Time) Duration {
// ```
pub fn (t Time) relative() string {
znow := now()
mut secs := znow.unix - t.unix()
mut secs := znow.unix() - t.unix()
mut prefix := ''
mut suffix := ''
if secs < 0 {
@ -239,7 +257,7 @@ pub fn (t Time) relative() string {
// ```
pub fn (t Time) relative_short() string {
znow := now()
mut secs := znow.unix - t.unix()
mut secs := znow.unix() - t.unix()
mut prefix := ''
mut suffix := ''
if secs < 0 {
@ -364,9 +382,9 @@ pub fn days_in_month(month int, year int) !int {
return res
}
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix }`).
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix is_local: false }`).
pub fn (t Time) debug() string {
return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} nanosecond: ${t.nanosecond:09} unix: ${t.unix:07} }'
return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} nanosecond: ${t.nanosecond:09} unix: ${t.unix:07} is_local: ${t.is_local} }'
}
// offset returns time zone UTC offset in seconds.

View file

@ -12,7 +12,7 @@ const time_to_test = time.Time{
fn test_now_format() {
t := time.now()
u := t.unix()
assert t.format() == time.unix(int(u)).format()
assert t.format() == time.unix(int(u)).utc_to_local().format()
}
fn test_format() {

View file

@ -15,7 +15,7 @@ fn test_tm_gmtoff() {
dump(t2)
dump(t1.nanosecond)
dump(t2.nanosecond)
diff := int(t1.unix() - t2.unix())
diff := int(t1.local_unix() - t2.unix())
dump(diff)
dump(info.tm_gmtoff)
assert diff in [info.tm_gmtoff - 1, info.tm_gmtoff, info.tm_gmtoff + 1]

View file

@ -90,17 +90,10 @@ fn test_unix() {
assert t6.hour == 6
assert t6.minute == 9
assert t6.second == 29
assert local_time_to_test.unix() == 332198622
assert utc_time_to_test.unix() == 332198622
}
fn test_format_rfc3339() {
// assert '1980-07-11T19:23:42.123Z'
res := local_time_to_test.format_rfc3339()
assert res.ends_with('23:42.123Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
// assert '1980-07-11T19:23:42.123Z'
utc_res := utc_time_to_test.format_rfc3339()
assert utc_res.ends_with('23:42.123Z')
@ -109,11 +102,6 @@ fn test_format_rfc3339() {
}
fn test_format_rfc3339_micro() {
res := local_time_to_test.format_rfc3339_micro()
assert res.ends_with('23:42.123456Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
utc_res := utc_time_to_test.format_rfc3339_micro()
assert utc_res.ends_with('23:42.123456Z')
assert utc_res.starts_with('1980-07-1')
@ -121,11 +109,6 @@ fn test_format_rfc3339_micro() {
}
fn test_format_rfc3339_nano() {
res := local_time_to_test.format_rfc3339_nano()
assert res.ends_with('23:42.123456789Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
utc_res := utc_time_to_test.format_rfc3339_nano()
assert utc_res.ends_with('23:42.123456789Z')
assert utc_res.starts_with('1980-07-1')

View file

@ -1398,7 +1398,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) (
}
ast.SelectorExpr {
if c.comptime.comptime_for_field_var != '' && cond.expr is ast.Ident {
if (cond.expr as ast.Ident).name == c.comptime.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] {
if (cond.expr as ast.Ident).name == c.comptime.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_embed', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] {
is_true = c.type_resolver.get_comptime_selector_bool_field(cond.field_name)
sb.write_string('${is_true}')
return is_true, true

View file

@ -221,10 +221,13 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
}
}
}
if node.is_expr {
c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type)
} else {
c.stmts(mut branch.stmts)
if !node.is_comptime || (node.is_comptime && comptime_match_branch_result) {
if node.is_expr {
c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type)
} else {
c.stmts(mut branch.stmts)
}
}
c.smartcast_mut_pos = token.Pos{}
c.smartcast_cond_pos = token.Pos{}

View file

@ -98,6 +98,21 @@ fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int
if obj.expr is ast.IfGuardExpr {
continue
}
if obj.expr is ast.UnsafeExpr && obj.expr.expr is ast.CallExpr
&& (obj.expr.expr as ast.CallExpr).is_method {
if left_var := scope.objects[obj.expr.expr.left.str()] {
if func := g.table.find_method(g.table.final_sym(left_var.typ),
obj.expr.expr.name)
{
if func.attrs.contains('reused') && left_var is ast.Var
&& left_var.expr is ast.CastExpr {
if left_var.expr.expr.is_literal() {
continue
}
}
}
}
}
g.autofree_variable(obj)
}
else {}

View file

@ -740,6 +740,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
g.writeln('\t${node.val_var}.unaliased_typ = ${int(unaliased_styp.idx())};\t// ${g.table.type_to_str(unaliased_styp)}')
g.writeln('\t${node.val_var}.is_pub = ${field.is_pub};')
g.writeln('\t${node.val_var}.is_mut = ${field.is_mut};')
g.writeln('\t${node.val_var}.is_embed = ${field.is_embed};')
g.writeln('\t${node.val_var}.is_shared = ${field.typ.has_flag(.shared_f)};')
g.writeln('\t${node.val_var}.is_atomic = ${field.typ.has_flag(.atomic_f)};')
@ -780,7 +781,12 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
if g.pref.translated && node.typ.is_number() {
g.writeln('_const_main__${val};')
} else {
g.writeln('${g.styp(node.typ)}__${val};')
node_sym := g.table.sym(node.typ)
if node_sym.info is ast.Alias {
g.writeln('${g.styp(node_sym.info.parent_type)}__${val};')
} else {
g.writeln('${g.styp(node.typ)}__${val};')
}
}
enum_attrs := sym.info.attrs[val]
if enum_attrs.len == 0 {

View file

@ -417,7 +417,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
if variant_sym.kind == .enum {
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('u64')}(*${var_data}${field_op}_${variant_typ}));')
} else if variant_sym.name == 'time.Time' {
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));')
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));')
} else {
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name(variant_typ)}(*${var_data}${field_op}_${variant_typ}));')
}
@ -442,7 +442,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
}
} else if variant_sym.name == 'time.Time' {
enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("${unmangled_variant_name}"));')
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));')
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));')
} else {
enc.writeln('\t\tcJSON_free(o);')
enc.writeln('\t\to = ${js_enc_name(variant_typ)}(*${var_data}${field_op}_${variant_typ});')
@ -954,9 +954,9 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
// time struct requires special treatment
// it has to be encoded as a unix timestamp number
if is_option {
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64((*(${g.base_type(field.typ)}*)(${prefix_enc}${op}${c_name(field.name)}.data)).__v_unix));')
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(time__Time_unix(*(${g.base_type(field.typ)}*)(${prefix_enc}${op}${c_name(field.name)}.data))));')
} else {
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}.__v_unix));')
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(time__Time_unix(${prefix_enc}${op}${c_name(field.name)})));')
}
} else {
if !field.typ.is_any_kind_of_pointer() {

View file

@ -0,0 +1,10 @@
VV_LOC void main__main(void) {
byteptr b = ((byteptr)("a"));
Array_u8 s = byteptr_vbytes(b, 1);
string _t1 = Array_u8_str(s); println(_t1); string_free(&_t1);
;
byteptr bb = ((byteptr)("a"));
string ss = byteptr_vstring(bb);
println(ss);
}

View file

@ -0,0 +1,2 @@
[97]
a

View file

@ -0,0 +1,10 @@
// vtest vflags: -autofree
fn main() {
b := byteptr(c'a')
s := unsafe { b.vbytes(1) }
println(s)
bb := byteptr(c'a')
ss := unsafe { bb.vstring() }
println(ss)
}

View file

@ -0,0 +1,5 @@
math.pi
math.pi // some comment
===output===
3.141592653589793
3.141592653589793

View file

@ -5,6 +5,8 @@ enum CharacterGroup {
special
}
type AnotherCharGroup = CharacterGroup
fn (self CharacterGroup) value() string {
return match self {
.chars { 'first' }
@ -33,3 +35,21 @@ fn test_main() {
assert values == [CharacterGroup.chars, CharacterGroup.alphanumerics, CharacterGroup.numeric,
CharacterGroup.special]
}
fn test_alias_enum() {
mut values := []EnumData{}
$for entry in AnotherCharGroup.values {
values << entry
}
assert values[0].value == int(CharacterGroup.chars)
assert values[0].name == CharacterGroup.chars.str()
assert values[1].value == int(CharacterGroup.alphanumerics)
assert values[1].name == CharacterGroup.alphanumerics.str()
assert values[2].value == int(CharacterGroup.numeric)
assert values[2].name == CharacterGroup.numeric.str()
assert values[3].value == int(CharacterGroup.special)
assert values[3].name == CharacterGroup.special.str()
}

View file

@ -1,4 +1,5 @@
struct App {
Inner
a string
b string
mut:
@ -12,6 +13,8 @@ pub mut:
h u8
}
struct Inner {}
@['foo/bar/three']
fn (mut app App) run() {
}
@ -85,13 +88,16 @@ fn test_comptime_for_fields() {
assert field.name in ['d', 'e']
}
if field.is_mut {
assert field.name in ['c', 'd', 'g', 'h']
assert field.name in ['c', 'd', 'g', 'h', 'Inner']
}
if field.is_pub {
assert field.name in ['e', 'f', 'g', 'h']
assert field.name in ['e', 'f', 'g', 'h', 'Inner']
}
if field.is_pub && field.is_mut {
assert field.name in ['g', 'h']
assert field.name in ['g', 'h', 'Inner']
}
if field.is_embed {
assert field.name == 'Inner'
}
if field.name == 'f' {
assert sizeof(field) == 8

View file

@ -0,0 +1,17 @@
module main
fn func[T]() bool {
$match T {
u8, u16 {
return true
}
$else {
// return false
$compile_error('fail')
}
}
}
fn test_comptime_match_eval_only_true_branch() {
assert func[u8]()
}

1
vlib/v/tests/runes.txt Normal file
View file

@ -0,0 +1 @@
Проба Abc 你好 🌍 123

View file

@ -224,6 +224,7 @@ pub fn (mut t TypeResolver) get_comptime_selector_bool_field(field_name string)
match field_name {
'is_pub' { return field.is_pub }
'is_mut' { return field.is_mut }
'is_embed' { return field.is_embed }
'is_shared' { return field_typ.has_flag(.shared_f) }
'is_atomic' { return field_typ.has_flag(.atomic_f) }
'is_option' { return field.typ.has_flag(.option) }