nomen-lang 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
1
  // Trait for collection types that can be iterated with `for x of collection`.
2
2
  // The loop variable receives int indices 0..length-1; use .at(i) to access values.
3
+ // (`List<T>` is deliberately NOT Enumerable: `for x of some_list` desugars to
4
+ // element iteration like arrays, which is what loop bodies almost always want.)
3
5
  /**
4
6
  * A trait for collections that can be iterated with `for x of collection`
5
7
  **/
package/dist/index.mjs CHANGED
@@ -21164,7 +21164,8 @@ function build_return_node$1(node, status, nir_value) {
21164
21164
  else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
21165
21165
  }
21166
21166
  if (needs_borrow_strdup) {
21167
- emit_strdup_string(status);
21167
+ if (is_view_value(node.value, status) && (node.value.node_type === "value" || node.value.node_type === "access")) emit_view_materialize_owned(status);
21168
+ else emit_strdup_string(status);
21168
21169
  if (!status.code.endsWith("\n")) status.code += "\n";
21169
21170
  status.last_result_is_heap = true;
21170
21171
  }
@@ -25996,6 +25997,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
25996
25997
  status.last_result_is_heap = false;
25997
25998
  emit_rhs_value$1(node.right_value, nir_rhs, status);
25998
25999
  if (!status.code.endsWith("\n")) status.code += "\n";
26000
+ const assign_lhs_type = status.scoped_declarations.find((d) => d.name === name)?.type ?? status.variable_types?.get(name);
26001
+ const rhs_is_view_into_owned = !node.swap && !node.operator && size === 16 && assign_lhs_type?.name === "string" && !assign_lhs_type.is_view && is_view_value(node.right_value, status);
26002
+ if (rhs_is_view_into_owned) {
26003
+ emit_view_materialize_owned(status);
26004
+ if (!status.code.endsWith("\n")) status.code += "\n";
26005
+ }
25999
26006
  const saves_fat_pair = size === 16;
26000
26007
  if (saves_fat_pair) status.code += `stp x0, x1, [sp, #-16]!\n`;
26001
26008
  else status.code += `str x0, [sp, #-16]!\n`;
@@ -26011,7 +26018,10 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
26011
26018
  const move_source = rhs_is_string_var && node.last_use_move && move_on_last_use_enabled() ? rhs_value.value : void 0;
26012
26019
  const explicit_move_source = !node.swap && !!rhs_value && rhs_value.is_moved && size === 16 && rhs_value.type?.name === "string" && !rhs_value.type?.is_view ? rhs_value.value : void 0;
26013
26020
  const borrow_needs_dup = !node.swap && size === 16 && is_string_borrow(node.right_value) && !!status.force_heap_strings?.has(name);
26014
- if (rhs_is_string_var && move_source === void 0 || borrow_needs_dup) emit_strdup_string(status);
26021
+ if (rhs_is_view_into_owned) {
26022
+ if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
26023
+ status.heap_strings.add(name);
26024
+ } else if (rhs_is_string_var && move_source === void 0 || borrow_needs_dup) emit_strdup_string(status);
26015
26025
  if (rhs_is_string_var || borrow_needs_dup) {
26016
26026
  if (move_source !== void 0) status.heap_strings?.delete(move_source);
26017
26027
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
@@ -34108,6 +34118,7 @@ function build_return_node(node, status, nir_value) {
34108
34118
  if (!returned_value_decl) returns_borrow_var = true;
34109
34119
  else if (is_string_borrow(returned_value_decl.value) || !!status.string_borrow_vars?.has(var_name)) returns_borrow_var = true;
34110
34120
  }
34121
+ const returns_view_value = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_array && is_view_value(node.value, status) && (node.value.node_type === "value" || node.value.node_type === "access");
34111
34122
  if (returns_borrowed_string) {
34112
34123
  const access = node.value.access;
34113
34124
  if (access.node_type === "access_func") {
@@ -34134,16 +34145,17 @@ function build_return_node(node, status, nir_value) {
34134
34145
  }
34135
34146
  }
34136
34147
  const returns_string_zero = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_array && node.value.node_type === "value" && (node.value.value === "0" || node.value.value === "null");
34137
- if (!returns_string_zero && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `nomen_str_dup(`;
34148
+ if (!returns_string_zero && !returns_view_value && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `nomen_str_dup(`;
34138
34149
  const wrap_view_string_return = !!ret_type.is_view && ret_type.name === "string" && !is_view_value(node.value, status);
34139
34150
  const expr_type_name = node.type?.name || "";
34140
34151
  const expr_is_simple = !status.structs.find((s) => s.name === expr_type_name && !s.is_simple_type);
34141
34152
  if (is_struct && expr_is_simple && !returns_borrowed_string && !ret_type.is_view) status.code += return_is_class ? `(struct ${mono_ret_name}*)` : `(struct ${mono_ret_name})`;
34142
34153
  if (wrap_view_string_return) status.code += `({ nomen_string _p = `;
34143
34154
  if (returns_string_zero) status.code += `(nomen_string){0,0}`;
34155
+ else if (returns_view_value) c_materialize_view_string(node.value, status);
34144
34156
  else emit_return_value(node.value, nir_value, status);
34145
34157
  if (wrap_view_string_return) status.code += `; nomen_view _v = { (void*)_p.ptr, _p.len }; _v; })`;
34146
- if (!returns_string_zero && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `)`;
34158
+ if (!returns_string_zero && !returns_view_value && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `)`;
34147
34159
  status.code += `;\n`;
34148
34160
  if (node.value.node_type === "func_call" && node.value.field_overrides?.length) emit_field_overrides("_return_val", node.value, build_node, status, "", ";\n");
34149
34161
  reclaim_all_c_scopes(status);
@@ -35884,9 +35896,15 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
35884
35896
  } else {
35885
35897
  if (lhs_is_string && !rhs_is_bare_value) {
35886
35898
  const temp = `_reassign_${status.label_counter = (status.label_counter ?? 0) + 1}`;
35899
+ const rhs_is_view = is_view_value(node.right_value, status);
35887
35900
  status.code += `nomen_string ${temp} = `;
35888
- emit_rhs_value(node.right_value, nir_rhs, status);
35901
+ if (rhs_is_view) c_materialize_view_string(node.right_value, status);
35902
+ else emit_rhs_value(node.right_value, nir_rhs, status);
35889
35903
  status.code += `;\nfree(${lhs_name}.ptr);\n${lhs_name} = ${temp};\n`;
35904
+ if (rhs_is_view) {
35905
+ if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
35906
+ status.heap_strings.add(lhs_name);
35907
+ }
35890
35908
  return;
35891
35909
  }
35892
35910
  status.code += `free(${lhs_name}.ptr);\n`;
@@ -35900,6 +35918,13 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
35900
35918
  if (rhs_is_bare_value) {
35901
35919
  if (lhs_is_class) {
35902
35920
  if (!node.swap) splice_decl_from_c_scopes(status, rhs.value);
35921
+ } else if (lhs_is_string && !node.swap && rhs.node_type === "value" && is_view_value(rhs, status)) {
35922
+ status.code += `${lhs_name} = `;
35923
+ c_materialize_view_string(rhs, status);
35924
+ status.code += `;\n`;
35925
+ if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
35926
+ status.heap_strings.add(lhs_name);
35927
+ return;
35903
35928
  } else if (!node.swap && rhs.is_moved) {
35904
35929
  const src_name = rhs.value;
35905
35930
  if (string_var_owns_heap(status, src_name)) {
@@ -37915,7 +37940,28 @@ function add_source(folder_path, file_path, inputs, lib_path) {
37915
37940
  source = source.replaceAll(/^import(.*)$/gm, (match, name) => {
37916
37941
  const trimmed = name.trim();
37917
37942
  if (trimmed === "System" || trimmed.startsWith("System::")) return match;
37918
- const import_file_path = `./${trimmed.split("::").join("/")}.nm`;
37943
+ const segments = trimmed.split("::").map((s) => s.trim()).filter((s) => s.length > 0);
37944
+ if (!segments.length) return "";
37945
+ const rel = segments.join("/");
37946
+ let is_dir = false;
37947
+ try {
37948
+ is_dir = fs.statSync(path.resolve(folder_path, rel)).isDirectory();
37949
+ } catch {}
37950
+ if (is_dir) {
37951
+ let names;
37952
+ try {
37953
+ names = fs.readdirSync(path.resolve(folder_path, rel));
37954
+ } catch {
37955
+ return "";
37956
+ }
37957
+ for (const name of names.sort()) {
37958
+ if (!name.endsWith(".nm")) continue;
37959
+ const import_file_path = `./${rel}/${name}`;
37960
+ if (!inputs.has(import_file_path)) add_source(folder_path, import_file_path, inputs, lib_path);
37961
+ }
37962
+ return "";
37963
+ }
37964
+ const import_file_path = `./${rel}.nm`;
37919
37965
  if (!inputs.has(import_file_path)) add_source(folder_path, import_file_path, inputs, lib_path);
37920
37966
  return "";
37921
37967
  });
@@ -40222,6 +40268,15 @@ function evaluate_operation(op, status) {
40222
40268
  }
40223
40269
  if (op.op === "<=" && left_decl?.upper_bound_inclusive_exprs?.length && target_str) {
40224
40270
  if (left_decl.upper_bound_inclusive_exprs.includes(target_str)) return true;
40271
+ if (left_decl.upper_bound_inclusive_exprs.some((u) => upper_implies(u, target_str))) return true;
40272
+ for (const u of left_decl.upper_bound_inclusive_exprs) {
40273
+ const v = parse_offset_expr(u);
40274
+ if (v && v.offset === 0 && u !== target_str) {
40275
+ const vdecl = status.values.findLast((vv) => vv.name === v.base);
40276
+ if (vdecl?.upper_bound_exprs?.includes(target_str)) return true;
40277
+ if (vdecl?.upper_bound_inclusive_exprs?.includes(target_str)) return true;
40278
+ }
40279
+ }
40225
40280
  }
40226
40281
  if (op.op === "<" && left_decl?.upper_bound_inclusive_exprs?.length && target_str && left_decl.upper_bound_inclusive_exprs.includes(target_str)) return "unsafe";
40227
40282
  if (left_decl?.upper_bound_expr && target_str) {
@@ -40531,6 +40586,7 @@ function is_nonnegative_access(node, status) {
40531
40586
  if (m) {
40532
40587
  const field_path = m[1];
40533
40588
  const offset_str = m[2];
40589
+ if (!field_path.includes(".")) return false;
40534
40590
  const last = field_path.split(".").at(-1);
40535
40591
  if (last && NON_NEGATIVE_FIELDS.has(last)) {
40536
40592
  if (!offset_str) return true;
@@ -40738,6 +40794,30 @@ function materialize_type(type, status) {
40738
40794
  //#endregion
40739
40795
  //#region ../src/check/utils/view_fields.ts
40740
40796
  /**
40797
+ * Whether the named struct (or any value struct embedded in its fields,
40798
+ * recursively) declares a `view T` field. A struct with view fields carries
40799
+ * non-owning borrows inside its bytes, which drives the borrow rules:
40800
+ * copies are sound (a pair copy aliases nothing owned), but escapes
40801
+ * (returning, outer-scope assignment) are checked against where those
40802
+ * borrows were taken.
40803
+ */
40804
+ function struct_has_view_fields(type_name, status) {
40805
+ if (!type_name) return false;
40806
+ const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type);
40807
+ return !!struct && has_view_fields(struct, status, /* @__PURE__ */ new Set());
40808
+ }
40809
+ function has_view_fields(struct, status, visited) {
40810
+ if (visited.has(struct.name)) return false;
40811
+ visited.add(struct.name);
40812
+ for (const field of struct.fields) {
40813
+ if (field.type.is_view) return true;
40814
+ if (field.type.is_ref || field.type.is_array) continue;
40815
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
40816
+ if (field_struct && has_view_fields(field_struct, status, visited)) return true;
40817
+ }
40818
+ return false;
40819
+ }
40820
+ /**
40741
40821
  * The root variable an access chain bottoms out at (`line.text` → "line",
40742
40822
  * `a.b.c` → "a", bare `x` → "x"). Returns undefined when the chain does not
40743
40823
  * bottom out in a plain name (e.g. a call receiver).
@@ -40859,6 +40939,23 @@ function view_borrows_root_at_self(sv) {
40859
40939
  return !!owners?.size && [...owners].every((o) => o === "self");
40860
40940
  }
40861
40941
  /**
40942
+ * Whether a value of this type can carry a non-owning view borrow: the type
40943
+ * itself is a `view`, a `ref` (a borrow by definition — keep the existing
40944
+ * taint), a struct declaring (transitively) `view T` fields, or a generic
40945
+ * instantiation with such an argument. Anything else (primitives, owned
40946
+ * strings, containers of owned values) owns its storage outright, so a call
40947
+ * producing it does not propagate its view arguments' borrows — tainting it
40948
+ * is a false positive (e.g. returning `slice_string(view)` from a function
40949
+ * returning owned `string`). Callers pass an unset/unknown type through as
40950
+ * carrying, preserving today's behavior where nothing is known.
40951
+ */
40952
+ function type_can_carry_view_borrow(type, status) {
40953
+ if (!type || !type.name) return true;
40954
+ if (type.is_view || type.is_ref) return true;
40955
+ if (struct_has_view_fields(type.name, status)) return true;
40956
+ return (type.type_args ?? []).some((t) => type_can_carry_view_borrow(t, status));
40957
+ }
40958
+ /**
40862
40959
  * Whether reading view fields of `root` is currently rejected because their
40863
40960
  * source was mutated (reassigned / ref-mutated).
40864
40961
  */
@@ -40983,7 +41080,7 @@ function check_declaration_node(decl, status) {
40983
41080
  if (type_from_value_node(decl.value, status)?.is_const_ref) decl.type.is_const_ref = true;
40984
41081
  }
40985
41082
  let view_borrows;
40986
- if (decl.value?.node_type === "func_call") {
41083
+ if (decl.value?.node_type === "func_call" && type_can_carry_view_borrow(decl.type, status)) {
40987
41084
  view_borrows = ctor_call_view_borrow(decl.value, status);
40988
41085
  if (view_borrows?.size) for (const [, info] of view_borrows) {
40989
41086
  const depth = info.depth ?? status.scope_depth;
@@ -43904,7 +44001,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
43904
44001
  lower_bound_inclusive_exprs = decl.lower_bound_inclusive_exprs;
43905
44002
  upper_bound_expr = decl.upper_bound_expr;
43906
44003
  lower_bound_expr = decl.lower_bound_expr;
43907
- alias_of = decl.alias_of;
44004
+ alias_of = decl.alias_of ?? expr_to_string(param, status);
43908
44005
  }
43909
44006
  } else if (param.node_type === "op") {
43910
44007
  const pop = param;
@@ -45189,7 +45286,7 @@ function check_assignment_node(assign, status) {
45189
45286
  const src_name = value_from_value_node(assign.right_value);
45190
45287
  const src_sv = assign.right_value.node_type === "value" ? status.values.findLast((v) => v.name === src_name) : void 0;
45191
45288
  if (src_sv?.has_view_borrows) propagate_view_borrows(left_value, src_sv);
45192
- } else if (assign.right_value.node_type === "func_call" && type_from_value_node(assign.right_value, status)?.name) {
45289
+ } else if (assign.right_value.node_type === "func_call" && type_from_value_node(assign.right_value, status)?.name && type_can_carry_view_borrow(type_from_value_node(assign.right_value, status), status)) {
45193
45290
  const infos = ctor_call_view_borrow(assign.right_value, status);
45194
45291
  if (infos?.size) {
45195
45292
  for (const [, info] of infos) {
@@ -45342,6 +45439,25 @@ function has_trait(type_name, trait_name, status) {
45342
45439
  if (!struct) return false;
45343
45440
  return struct.traits.includes(trait_name);
45344
45441
  }
45442
+ /**
45443
+ * The element type of a `List<T>` value, from either the generic annotation
45444
+ * (`List` + type args) or the monomorphized struct (`List_Diff` via its
45445
+ * recorded source args) — mirroring the lookup in check_access_node.
45446
+ * Returns undefined when the List struct isn't in scope at all (e.g. bare
45447
+ * parse without the System library) or the element can't be recovered, in
45448
+ * which case the loop falls through to the array-or-Enumerable error.
45449
+ */
45450
+ function list_element_type(t, status) {
45451
+ if (t.name === "List") {
45452
+ const generic = status.structs.find((s) => s.name === "List");
45453
+ if (!generic || !generic.functions.some((f) => f.name === "at")) return void 0;
45454
+ return t.type_args?.[0];
45455
+ }
45456
+ const struct = t.name ? status.structs.find((s) => s.name === t.name) : void 0;
45457
+ if (!struct?.name.startsWith("List_")) return void 0;
45458
+ if (!struct.functions.some((f) => f.name === "at")) return void 0;
45459
+ return struct.source_type_args?.[0];
45460
+ }
45345
45461
  function check_for_loop_node(for_loop, status) {
45346
45462
  if (for_loop.list && !(for_loop.list instanceof RangeNode) && for_loop.list.node_type === "value") {
45347
45463
  const list_type = type_from_value_node(for_loop.list, status);
@@ -45353,13 +45469,23 @@ function check_for_loop_node(for_loop, status) {
45353
45469
  check_for_loop_node(for_loop, status);
45354
45470
  return;
45355
45471
  }
45472
+ const list_elem = list_element_type(list_type, status);
45473
+ if (list_elem && !enumerable) {
45474
+ if (for_loop.item_is_ref) {
45475
+ add_error(status, `'ref' iteration is not supported for List<T> — index explicitly (for i of 0..xs.length) and use .set to write back`, for_loop.item.start);
45476
+ return;
45477
+ }
45478
+ desugar_array_for_loop(for_loop, list_type, list_elem);
45479
+ check_for_loop_node(for_loop, status);
45480
+ return;
45481
+ }
45356
45482
  }
45357
45483
  let for_status = clone_status(status);
45358
45484
  if (for_loop.list) {
45359
45485
  check_node(for_loop.list, for_status);
45360
45486
  const list_type = type_from_value_node(for_loop.list, for_status);
45361
45487
  const is_enumerable = list_type.name ? has_trait(list_type.name, "Enumerable", for_status) : false;
45362
- if (!list_type.is_array && !is_enumerable && list_type.name) add_error(for_status, `For loop list must be an array or Enumerable, not ${list_type.name}`, for_loop.list.start);
45488
+ if (!list_type.is_array && !is_enumerable && list_type.name) add_error(for_status, `For loop list must be an array, List, or Enumerable, not ${list_type.name}`, for_loop.list.start);
45363
45489
  if (for_loop.item_is_ref && list_type.is_array && for_loop.list.node_type === "value") {
45364
45490
  const list_name = for_loop.list.value;
45365
45491
  const list_value = for_status.values.findLast((v) => v.name === list_name);
@@ -45411,17 +45537,17 @@ function check_for_loop_node(for_loop, status) {
45411
45537
  * break/continue so mutations persist on all exit paths. (The write-back is
45412
45538
  * skipped on `return`, matching Rust's copy semantics.)
45413
45539
  */
45414
- function desugar_array_for_loop(for_loop, array_type) {
45540
+ function desugar_array_for_loop(for_loop, array_type, elem_type) {
45415
45541
  const list = for_loop.list;
45416
45542
  const start = list.start;
45417
45543
  const original_item = for_loop.item;
45418
45544
  const idx_name = `__for_idx_${original_item.value}`;
45419
45545
  const is_ref = !!for_loop.item_is_ref;
45420
45546
  for_loop.item = new ValueNode(original_item.start, idx_name);
45421
- for_loop.list = new RangeNode(start, new ValueNode(start, "0"), array_type.storage_kind === "heap_array" || !array_type.length ? new AccessNode(start, clone_node(list), new AccessFieldNode(start, "length")) : clone_node(array_type.length));
45547
+ for_loop.list = new RangeNode(start, new ValueNode(start, "0"), elem_type !== void 0 || array_type.storage_kind === "heap_array" || !array_type.length ? new AccessNode(start, clone_node(list), new AccessFieldNode(start, "length")) : clone_node(array_type.length));
45422
45548
  const at_call = new AccessFunctionCallNode(start, "at", new Type(""), [new ValueNode(start, idx_name)]);
45423
45549
  const at_access = new AccessNode(start, clone_node(list), at_call);
45424
- const decl = new DeclarationNode(original_item.start, "private", is_ref ? "var" : "const", original_item.value, new Type(array_type.name), at_access);
45550
+ const decl = new DeclarationNode(original_item.start, "private", is_ref ? "var" : "const", original_item.value, elem_type ?? new Type(array_type.name), at_access);
45425
45551
  decl.is_loop_iterator = true;
45426
45552
  for_loop.statements.unshift(decl);
45427
45553
  if (is_ref) {
@@ -46031,7 +46157,7 @@ function check_return_node(ret, status) {
46031
46157
  if (!safe_view_from_self && !explicit_mov) add_error(status, `cannot return a borrowed reference — use 'move' (with swap) to transfer ownership`, ret.value.start);
46032
46158
  }
46033
46159
  } else if (func && ret.value.node_type === "func_call") {
46034
- const infos = ctor_call_view_borrow(ret.value, status);
46160
+ const infos = type_can_carry_view_borrow(ret.type, status) ? ctor_call_view_borrow(ret.value, status) : void 0;
46035
46161
  if (infos?.size && ![...infos.keys()].every((o) => o === "self")) add_error(status, `cannot return '${ret.type.name}' — its 'view' field(s) borrow from this scope`, ret.value.start);
46036
46162
  }
46037
46163
  if (func) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nomen-lang",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "The CLI for the Nomen programming language.",
5
5
  "keywords": [],
6
6
  "license": "ISC",