ci: fix macOS bootstrap and example builds (#31205)
* fastc: dereference pointer-returning calls with indexed arguments * builtin: avoid FastC symbol collision in two-byte string search * tools: build the macOS tray example as a project folder * tests: align parser and Wasm audits with supported features * transform: preserve caller types in generic specializations * cgen: preserve source module identity in cached declarations * cgen: resolve cached capture array fields in their source module * test: assert the default SLH-DSA key description
This commit is contained in:
1 parent
aaa8115298
commit
9db0ba48fa
16 files changed
+249
-44
No files matched your search
@@ -109,6 +109,9 @@ It checkpoints the successful instrumented compiler build. A retry runs the fail
|
||||
self-compilation without rebuilding `v2`, unless that binary is missing or changed.
|
||||
Other non-test commands retain their existing task-level retry behavior.
|
||||
|
||||
On macOS, `build-examples` builds `examples/macos_tray` as a project folder so its
|
||||
platform-specific source is included alongside the main program.
|
||||
|
||||
A resumed pass is not a fresh validation of earlier tasks or tests against shared
|
||||
source changes. Compiler and library edits deliberately preserve progress. Use
|
||||
`ci --reset` for a complete check, after switching branches, or after removing build
|
||||
|
||||
@@ -24,14 +24,18 @@ fn main() {
|
||||
mut requested_flags := vflags.tokenize_to_args(os.getenv('VFLAGS'))
|
||||
requested_flags << vflags.tokenize_to_args(params)
|
||||
strict_v3 := '-old-compiler' !in requested_flags
|
||||
mut skip_prefixes := efolders.map(normalised_vroot_path(it))
|
||||
res := testing.v_build_failing_skipped(params, 'examples', skip_prefixes, fn [strict_v3] (mut session testing.TestSession) {
|
||||
mut project_folders := efolders.clone()
|
||||
$if macos {
|
||||
project_folders << 'examples/macos_tray'
|
||||
}
|
||||
mut skip_prefixes := project_folders.map(normalised_vroot_path(it))
|
||||
res := testing.v_build_failing_skipped(params, 'examples', skip_prefixes, fn [strict_v3, project_folders] (mut session testing.TestSession) {
|
||||
if strict_v3 {
|
||||
// V3 has only a limited JavaScript compatibility generator. Keep full-backend
|
||||
// examples visible as skips unless the legacy compiler was explicitly selected.
|
||||
session.skip_files << session.files.filter(it.ends_with('.js.v'))
|
||||
}
|
||||
for x in efolders {
|
||||
for x in project_folders {
|
||||
pathsegments := x.split_any('/')
|
||||
fpath := os.real_path(os.join_path(vroot, ...pathsegments))
|
||||
session.skip_files = session.skip_files.filter(it != fpath)
|
||||
|
||||
@@ -1527,7 +1527,7 @@ fn (s string) index_multibyte(p string) int {
|
||||
return s.index_kmp(p)
|
||||
}
|
||||
if p.len == 2 {
|
||||
return s.index_pair(p)
|
||||
return s.index_two_bytes(p)
|
||||
}
|
||||
// Short needles: scanning for the first byte and comparing the rest in place
|
||||
// needs no prefix table, and costs at most p.len compares per position.
|
||||
@@ -1550,7 +1550,7 @@ fn (s string) index_multibyte(p string) int {
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn (s string) index_pair(p string) int {
|
||||
fn (s string) index_two_bytes(p string) int {
|
||||
return string_index_pair(s.str, s.len, unsafe { p.str[0] }, unsafe { p.str[1] })
|
||||
}
|
||||
|
||||
|
||||
@@ -24269,6 +24269,9 @@ fn (mut g FlatGen) fixed_array_typedef_type_module(name string, fallback string)
|
||||
if info := g.struct_decl_infos[name] {
|
||||
return info.module
|
||||
}
|
||||
if module_name := g.tc.struct_modules[name] {
|
||||
return module_name
|
||||
}
|
||||
mod := module_from_qualified_name(name)
|
||||
if mod.len > 0 {
|
||||
return mod
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_generated_capture_struct_uses_source_module_for_fixed_array_size() {
|
||||
mut a := flat.FlatAst.new()
|
||||
value := a.add_val(.int_literal, '4')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'cached_support'
|
||||
tc.const_exprs['fixed_array_size'] = value
|
||||
tc.const_modules['fixed_array_size'] = 'main'
|
||||
context_name := '__anon_fn_0_Ctx'
|
||||
tc.struct_modules[context_name] = 'main'
|
||||
tc.structs[context_name] = [types.StructField{
|
||||
name: 'values'
|
||||
typ: types.Type(types.Pointer{
|
||||
base_type: types.Type(types.ArrayFixed{
|
||||
elem_type: types.Type(types.int_)
|
||||
len_expr: 'fixed_array_size'
|
||||
})
|
||||
})
|
||||
}]
|
||||
// Simulate the spelling first requested while generating cached support code.
|
||||
assert tc.c_type(tc.structs[context_name][0].typ) == 'Array_fixed_i64_fixed_array_size*'
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.emit_struct(context_name)
|
||||
assert g.sb.str().contains('Array_fixed_i64_4* values;'), g.sb.str()
|
||||
assert tc.cur_module == 'cached_support'
|
||||
}
|
||||
|
||||
fn test_callback_field_lookup_preserves_generic_argument_module() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'genericmodule'
|
||||
caller_callback := types.Type(types.FnType{ return_type: types.Type(types.int_) })
|
||||
imported_callback := types.Type(types.FnType{ return_type: types.Type(types.string_) })
|
||||
tc.structs['genericmodule.Each[Row]'] = [types.StructField{
|
||||
name: 'visit'
|
||||
typ: caller_callback
|
||||
}]
|
||||
tc.structs['genericmodule.Each[genericmodule.Row]'] = [types.StructField{
|
||||
name: 'visit'
|
||||
typ: imported_callback
|
||||
}]
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
caller := types.Type(types.Struct{ name_id: flat.source_type_id('genericmodule.Each[main.Row]') })
|
||||
imported := types.Type(types.Struct{ name_id: flat.source_type_id('genericmodule.Each[genericmodule.Row]') })
|
||||
assert g.field_type(caller, 'visit')? == caller_callback
|
||||
assert g.field_type(imported, 'visit')? == imported_callback
|
||||
assert g.field_type(caller, 'missing') == none
|
||||
// An exact specialization takes precedence over the bare main spelling.
|
||||
tc.structs['genericmodule.Each[main.Row]'] = [types.StructField{
|
||||
name: 'visit'
|
||||
typ: imported_callback
|
||||
}]
|
||||
assert g.field_type(caller, 'visit')? == imported_callback
|
||||
}
|
||||
+5
-1
@@ -13546,7 +13546,11 @@ fn (g &FlatGen) field_type(base_type types.Type, field_name string) ?types.Type
|
||||
if struct_name.len == 0 {
|
||||
return none
|
||||
}
|
||||
fields := g.tc.structs[struct_name] or { return none }
|
||||
fields := g.tc.structs[struct_name] or {
|
||||
// Checked generic arguments can retain `main.` to distinguish the caller's
|
||||
// type from an imported namesake; materialized structs use bare main names.
|
||||
g.tc.structs[strip_main_module_qualifier(struct_name)] or { return none }
|
||||
}
|
||||
for field in fields {
|
||||
if field.name == field_name {
|
||||
return field.typ
|
||||
|
||||
@@ -8308,6 +8308,11 @@ fn (mut g FlatGen) write_struct_field(_struct_name string, f types.StructField)
|
||||
g.writeln('\t${qualifier}${ct}* ${g.cname(f.name)};')
|
||||
return
|
||||
}
|
||||
if _ := array_fixed_type(types.unwrap_all_pointers(field_type.base_type)) {
|
||||
// Cached C spellings can retain a length evaluated in another module.
|
||||
g.writeln('\t${qualifier}${g.value_c_type(field_type)} ${g.cname(f.name)};')
|
||||
return
|
||||
}
|
||||
}
|
||||
if field_type is types.FnType {
|
||||
c_abi_fn := g.struct_field_c_abi_fn_ptr_type(flat.source_type_id(_struct_name), f.name) or {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
module fastc
|
||||
|
||||
import os
|
||||
import v.cmdexec
|
||||
|
||||
fn test_fastc_pointer_call_with_indexed_arguments_is_passed_by_value() {
|
||||
$if !macos && !linux {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'fastc_indexed_call_argument_${os.getpid()}')
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
source := os.join_path(root, 'main.v')
|
||||
os.write_file(source, r'module main
|
||||
|
||||
struct Record {
|
||||
value int
|
||||
}
|
||||
type Value = Record | int
|
||||
|
||||
fn record(value int) &Record { return &Record{value: value} }
|
||||
fn boxed(value &Value, expected int) &Value { assert expected == 42; return value }
|
||||
fn read_record(value Record) int { return value.value }
|
||||
fn read_value(value Value) int {
|
||||
return match value {
|
||||
Record { value.value }
|
||||
int { value }
|
||||
}
|
||||
}
|
||||
fn identity(value int) int { return value }
|
||||
fn next(mut calls int) int { calls++; return 0 }
|
||||
fn slice_len(values []int) int { return values.len }
|
||||
fn forward(mut values []int) int { return slice_len(values[1..]) }
|
||||
fn grouped_forward(mut values []int) int { return slice_len((values[1..])) }
|
||||
fn main() {
|
||||
mut values := [42, 43]
|
||||
mut calls := 0
|
||||
assert read_record(record(values[next(mut calls)])) == 42
|
||||
assert calls == 1
|
||||
value := Value(Record{value: 42})
|
||||
assert read_value(boxed(&value, identity(values[next(mut calls)]))) == 42
|
||||
assert calls == 2
|
||||
assert read_record((record(values[next(mut calls)]))) == 42
|
||||
assert calls == 3
|
||||
assert forward(mut values) == 1
|
||||
assert grouped_forward(mut values) == 1
|
||||
}
|
||||
') or { panic(err) }
|
||||
program := os.join_path(root, 'program')
|
||||
build := cmdexec.run_in_merged_with_env(@VEXE, ['-b', 'fastc', '-d', 'fastc_real_builtin',
|
||||
'-nocache', '-gc', 'none', '-o', program, source], @VEXEROOT, {
|
||||
'VEXE': @VEXE
|
||||
'VFLAGS': ''
|
||||
'VOSARGS': ''
|
||||
'V_MACOS_V3_NO_FALLBACK': '1'
|
||||
'V_C_ERROR_BUG_REPORT_DISABLED': '1'
|
||||
})
|
||||
assert build.exit_code == 0, build.output
|
||||
run := cmdexec.run(program, [])
|
||||
assert run.exit_code == 0, run.output
|
||||
}
|
||||
@@ -4932,11 +4932,12 @@ fn (g &Parser) render_call_argument_expression(tokens []FastcExpressionToken, ex
|
||||
// The `.lsbr` guard below would wrongly skip this because the chain indexes an array.
|
||||
return '*(${rendered})'
|
||||
}
|
||||
if flat.source_type_query(actual_type).has(.c_pointer_suffix) && expected_type_source_id == fastc_trim_pointer_type(actual_type) && expected_type_source_id != flat.source_type_voidptr && !fastc_expression_tokens_contain(tokens, .lsbr) {
|
||||
if flat.source_type_query(actual_type).has(.c_pointer_suffix) && expected_type_source_id == fastc_trim_pointer_type(actual_type) && expected_type_source_id != flat.source_type_voidptr && (!fastc_expression_tokens_contain(tokens, .lsbr) || fastc_tokens_are_plain_call(fastc_strip_paren_tokens(tokens))) {
|
||||
// V automatically dereferences an explicit reference when a by-value
|
||||
// parameter is expected (`s &string` passed to `log_line(s string)`). A
|
||||
// slice of a mutable array is already a by-value array even though the
|
||||
// receiver local retains its pointer type during inference.
|
||||
// Indexes inside call arguments do not change the call's pointer result.
|
||||
return '*(${rendered})'
|
||||
}
|
||||
if flat.source_type_query(expected_type_source_id).has(.c_pointer_suffix) && actual_type == flat.source_type_voidptr && rendered.trim_space() == 'NULL' {
|
||||
|
||||
@@ -701,43 +701,39 @@ fn main() {
|
||||
refuses: 'ssa: unknown function `f64__strg`'
|
||||
},
|
||||
StubCase{
|
||||
tag: 'string_split'
|
||||
stub: 'strings.split - no synthetic body, source body panics'
|
||||
source: '
|
||||
tag: 'string_split'
|
||||
stub: 'strings.split - source body'
|
||||
source: '
|
||||
fn main() {
|
||||
sp := "a,b,c".split(",")
|
||||
println(sp)
|
||||
println(sp.len)
|
||||
}
|
||||
'
|
||||
native: "['a', 'b', 'c']\n3\n"
|
||||
refuses: 'ssa: unknown function `split`'
|
||||
native: "['a', 'b', 'c']\n3\n"
|
||||
},
|
||||
StubCase{
|
||||
tag: 'string_repeat'
|
||||
stub: 'strings.repeat - no synthetic body, source body panics'
|
||||
source: '
|
||||
tag: 'string_repeat'
|
||||
stub: 'strings.repeat - source body'
|
||||
source: '
|
||||
fn main() {
|
||||
s := "ab".repeat(3)
|
||||
println(s)
|
||||
}
|
||||
'
|
||||
native: 'ababab\n'
|
||||
refuses: 'ssa: unknown function `string__repeat`'
|
||||
native: 'ababab\n'
|
||||
},
|
||||
StubCase{
|
||||
tag: 'string_index'
|
||||
stub: 'strings.index - no synthetic body, source body panics'
|
||||
source: stub_source_string_index
|
||||
native: 'Option(2)\nOption(none)\n'
|
||||
refuses: 'ssa: unknown function `index`'
|
||||
tag: 'string_index'
|
||||
stub: 'strings.index - source body'
|
||||
source: stub_source_string_index
|
||||
native: 'Option(2)\nOption(none)\n'
|
||||
},
|
||||
StubCase{
|
||||
tag: 'string_reverse'
|
||||
stub: 'strings.reverse - no synthetic body, source body panics'
|
||||
source: stub_source_string_reverse
|
||||
native: 'cba\n399\n'
|
||||
refuses: 'ssa: unknown function `reverse`'
|
||||
tag: 'string_reverse'
|
||||
stub: 'strings.reverse - source body'
|
||||
source: stub_source_string_reverse
|
||||
native: 'cba\n399\n'
|
||||
},
|
||||
StubCase{
|
||||
tag: 'pointer_strings'
|
||||
|
||||
@@ -2,20 +2,6 @@ module parser
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_vml_declared_type_ids_keep_exact_spelling_dispatch() {
|
||||
for type_id in [flat.source_type_f64, flat.source_type_f32, flat.source_type_int] {
|
||||
assert vml_property_use(VmlProperty{ expr: &VmlExpr{}, declared_type: type_id }) == .number
|
||||
}
|
||||
assert vml_property_use(VmlProperty{ expr: &VmlExpr{}, declared_type: flat.source_type_bool }) == .bool_
|
||||
assert vml_property_use(VmlProperty{ expr: &VmlExpr{}, declared_type: flat.source_type_string }) == .string_
|
||||
assert vml_property_use(VmlProperty{ expr: &VmlExpr{}, declared_type: flat.source_type_id('color') }) == .color
|
||||
// Explicit unknown or padded annotations override the name-based inference.
|
||||
for spelling in [' int ', 'pkg.int', 'unknown'] {
|
||||
assert vml_property_use(VmlProperty{ expr: &VmlExpr{}, name: 'width', declared_type: flat.source_type_id(spelling) }) == .raw
|
||||
}
|
||||
assert vml_property_use(VmlProperty{ expr: &VmlExpr{}, name: 'width' }) == .number
|
||||
}
|
||||
|
||||
fn test_named_constraint_ids_preserve_sparse_parameter_order() {
|
||||
constraint := flat.source_type_id('pkg.Box[&Item]')
|
||||
assert named_constraints([u32(0), constraint]) == [u32(0), constraint]
|
||||
|
||||
@@ -12635,7 +12635,8 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args_ids []u32, i
|
||||
}
|
||||
mut cloned_call_args := []u32{cap: node.call_type_args().len}
|
||||
for arg in node.call_type_args() {
|
||||
cloned_call_args << t.subst_type(arg, args_ids)
|
||||
substituted_arg := t.subst_type(arg, args_ids)
|
||||
cloned_call_args << t.lock_colliding_main_substitution_type_text(arg, substituted_arg, t.cur_module, t.active_generic_params)
|
||||
}
|
||||
has_source_value := node.source_value != 0 || node.kind in [.struct_init, .assoc, .cast_expr,
|
||||
.as_expr, .is_expr, .map_init, .array_init, .array_literal, .sizeof_expr, .typeof_expr,
|
||||
@@ -13339,7 +13340,9 @@ fn (mut t Transformer) substitute_cloned_generic_call_type_args(node flat.Node,
|
||||
&& !generic_text_contains_param(arg, t.active_generic_params) {
|
||||
continue
|
||||
}
|
||||
substituted := flat.source_type_name(t.resolve_substituted_type_text(t.subst_type(arg_source_id_10, args_ids)))
|
||||
concrete := t.subst_type(arg_source_id_10, args_ids)
|
||||
locked := t.lock_colliding_main_substitution_type_text(arg_source_id_10, concrete, t.cur_module, t.active_generic_params)
|
||||
substituted := flat.source_type_name(t.resolve_substituted_type_text(locked))
|
||||
if substituted.len == 0 || substituted == arg
|
||||
|| t.generic_args_have_placeholders(flat.TypeTextList.from_names([substituted]).ids) {
|
||||
continue
|
||||
|
||||
@@ -1365,3 +1365,24 @@ type Choice = int | string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_forwarded_generic_call_keeps_caller_context_identity() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_node(flat.Node{ kind: .ident, value: 'forward' })
|
||||
arg := a.add_node(flat.Node{ kind: .ident, value: 'T' })
|
||||
start := a.children.len
|
||||
a.children << callee
|
||||
a.children << arg
|
||||
index := flat.Node{ kind: .index, children_start: start, children_count: 2 }
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['Context'] = []types.StructField{}
|
||||
tc.structs['callee.Context'] = []types.StructField{}
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.cur_module = 'callee'
|
||||
t.active_generic_params = ['T']
|
||||
t.structs['Context'] = StructInfo{}
|
||||
t.structs['callee.Context'] = StructInfo{ module: 'callee' }
|
||||
mut children := [callee, arg]
|
||||
t.substitute_cloned_generic_call_type_args(index, mut children, [flat.source_type_id('Context')])
|
||||
assert t.a.node(children[1]).value_text() == 'main.Context'
|
||||
}
|
||||
@@ -7090,6 +7090,18 @@ fn (tc &TypeChecker) qualify_type_text_impl(typ string, resolution bool, generic
|
||||
}
|
||||
}
|
||||
if resolution && clean.contains('.') {
|
||||
// Keep concrete caller types locked while resolving a foreign generic body.
|
||||
// Generic bases retain their canonical specialization spelling below.
|
||||
if tc.cur_module != 'main' && tc.cur_module.len > 0
|
||||
&& clean.starts_with('main.') && !clean['main.'.len..].contains('.') {
|
||||
base := clean['main.'.len..]
|
||||
if _ := tc.resolve_import_alias('main') {
|
||||
} else if base !in tc.struct_generic_params && base !in tc.sum_generic_params
|
||||
&& base !in tc.interface_generic_params && base !in tc.type_alias_generic_params
|
||||
&& tc.qualify_candidate_type_exists(base) {
|
||||
return clean
|
||||
}
|
||||
}
|
||||
resolved := flat.source_type_name(tc.resolve_imported_type_text(flat.source_type_id(clean)))
|
||||
if resolved != clean {
|
||||
return resolved
|
||||
|
||||
@@ -2,6 +2,31 @@ module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_generic_receiver_field_keeps_explicit_main_argument() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_module = 'foreign'
|
||||
tc.cur_file = 'foreign.v'
|
||||
tc.structs['Record'] = []StructField{}
|
||||
tc.structs['foreign.Record'] = []StructField{}
|
||||
tc.struct_generic_params['foreign.Slot'] = ['T']
|
||||
tc.structs['foreign.Slot'] = [StructField{
|
||||
name: 'value'
|
||||
typ: unknown_type('generic placeholder `T`')
|
||||
}]
|
||||
assert tc.qualified_generic_suffix([flat.source_type_id('main.Record')]) == '[main.Record]'
|
||||
receiver := tc.parse_scope_param_type(flat.source_type_id('&foreign.Slot[main.Record]'))
|
||||
assert receiver.name() == '&foreign.Slot[main.Record]'
|
||||
field := tc.struct_field_type(flat.source_type_id('foreign.Slot[main.Record]'), 'value') or {
|
||||
panic('missing generic field')
|
||||
}
|
||||
assert field.name() == 'Record'
|
||||
// A real import named main must still resolve through that import.
|
||||
tc.register_file_import('main', 'other')
|
||||
tc.structs['other.Record'] = []StructField{}
|
||||
assert tc.qualified_generic_suffix([flat.source_type_id('main.Record')]) == '[other.Record]'
|
||||
}
|
||||
|
||||
fn test_qualified_generic_argument_ids_keep_nested_commas_and_file_imports() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
@@ -42,3 +67,22 @@ fn test_generic_alias_argument_ids_keep_substitution_order_and_arity_fallback()
|
||||
assert fallback.base_type.source_type_id() == flat.source_type_u64
|
||||
}
|
||||
}
|
||||
|
||||
fn test_main_generic_base_keeps_canonical_specialization_spelling() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_module = 'foreign'
|
||||
tc.struct_generic_params['Box'] = ['T']
|
||||
tc.structs['Box'] = []StructField{}
|
||||
tc.struct_generic_params['foreign.Box'] = ['T']
|
||||
tc.structs['foreign.Box'] = []StructField{}
|
||||
assert tc.qualified_generic_suffix([flat.source_type_id('main.Box[string]')]) == '[Box[string]]'
|
||||
}
|
||||
|
||||
fn test_main_callback_type_keeps_unqualified_diagnostic_spelling() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.structs['Context'] = []StructField{}
|
||||
assert flat.source_type_name(tc.qualify_resolution_type_text(flat.source_type_id('fn (mut main.Context) bool'))) == 'fn(mut Context) bool'
|
||||
}
|
||||
@@ -73,7 +73,7 @@ fn test_privkey_signing() ! {
|
||||
fn test_basic_properties() ! {
|
||||
mut pv := PrivateKey.new()!
|
||||
desc := key_description(pv.key)
|
||||
unsafe { desc.vstring() } == 'OpenSSL SLH-DSA-SHA2-128f implementation'
|
||||
assert unsafe { desc.vstring() } == 'OpenSSL SLH-DSA-SHA2-128s implementation'
|
||||
|
||||
n := C.EVP_PKEY_is_a(pv.key, ln_slhdsa_sha2_128s)
|
||||
assert n == 1 // true
|
||||
|
||||
Reference in new issue
Block a user