diff --git a/docs/reference/language/field-visibility-and-delegation.md b/docs/reference/language/field-visibility-and-delegation.md new file mode 100644 index 00000000..43d6b81c --- /dev/null +++ b/docs/reference/language/field-visibility-and-delegation.md @@ -0,0 +1,64 @@ +# フィールド可視性とデリゲーション設計(提案/仕様草案) + +本書は Nyash 言語の「フィールド可視性」と「デリゲーション(from/override)」の設計をまとめた仕様草案です。実装は段階的に進めます。 + +## 1. フィールド可視性(Blocks) +- 構文 + ```nyash + box User { + private { age, passwordHash, internalCache } + public { name, email, displayName } + + init { name, email, displayName, age } + // ... methods ... + } + ``` +- ルール + - `private`: box 内のメソッドからのみアクセス可(外部アクセス不可) + - `public`: 外部から `obj.field` で参照可 + - いずれにも属さないフィールドはエラー(明示主義) + - `me.field` は可視性に関わらず常に許可(自身の内部) + - init は現状どおり public(将来 `public/private init` の導入は別途検討) +- エラー例 + - 外部から `user.age` → Compile/Interpret Error: private field access + - 親の private フィールド参照 → Error: parent private field not accessible + +実装フェーズ(予定) +1. パーサ: `private { ... }` / `public { ... }` の受理、AST/宣言モデルに可視性付与 +2. 解決/実行: 外部アクセス時に public のみ許可、`me.field` は常にOK +3. MIR/VM: 既存の `RefGet/RefSet` に可視性チェックフックを追加 + +## 2. デリゲーション(from/override) +目標: ビルトイン/プラグイン/ユーザー定義の全Boxで、同一の書き味(from/override/super呼び出し)を提供しつつ、安全性を担保する。 + +- 基本方針 + - 継承モデルは維持(「委譲の糖衣」ではない) + - 親の内部フィールドは不可視(private は子からも見えない) + - メソッド解決規則は統一:子の override → なければ親へ解決 + - 親がビルトイン/プラグインの場合、親メソッド呼び出しは `BoxCall` に lower(ローダ経由) + +- 書式例 + ```nyash + box MyFile from FileBox { + override write(x) { + // 前処理 + from FileBox.write(x) + // 後処理 + } + } + ``` + +- 安全性 + - 親の状態は親の実体としてのみ存在(フィールド継承しない) + - `from Parent.method()` は親タイプ名での明示ディスパッチ(暗黙の内包はしない) + - MIR では `BoxCall` を生成し、VM/Interpreter はローダへ委譲 + +実装フェーズ(予定) +1. MIR Lowering: 親がビルトイン/プラグインの `from` を常に `BoxCall` へ(現状踏襲) +2. VM/Interpreter: 既存どおりローダ委譲(Handle/BoxRef を統一処理) + +## 3. 参考 +- BoxRef/Handle 仕様: `docs/reference/plugin-system/boxref-behavior.md` +- nyash.toml v2.1–v2.2: `docs/reference/plugin-system/nyash-toml-v2_1-spec.md` +- 実装箇所(予定): `src/parser/declarations/box_definition.rs`, `src/core/model.rs`, `src/interpreter/expressions/access.rs`, `src/mir/*`, `src/backend/vm.rs` + diff --git a/docs/reference/plugin-system/boxref-behavior.md b/docs/reference/plugin-system/boxref-behavior.md new file mode 100644 index 00000000..87c4a4c5 --- /dev/null +++ b/docs/reference/plugin-system/boxref-behavior.md @@ -0,0 +1,53 @@ +# BoxRef/Handle Behavior (v2.1–v2.2) + +本書は、プラグインBoxの引数/返り値としてのBox参照(BoxRef/Handle)の扱いと、VM/インタプリタ/ローダ/プラグインにまたがる設計上の注意点をまとめます。 + +## 1. TLV仕様(BID-1) +- ヘッダ: `ver:u16=1, argc:u16` +- エントリ: `tag:u8, rsv:u8=0, size:u16, payload...` +- 主要タグ: + - `6 = String(UTF-8)`, `7 = Bytes` + - `8 = Handle(BoxRef)` → payload: `type_id:u32 + instance_id:u32`(計8バイト, LE) + - `2 = I32`, `3 = I64`, `1 = Bool`, `9 = Void` + +## 2. nyash.toml(v2.1〜) +- 引数宣言に `args=[{ kind="box", category="plugin" }]` を追加可能。 +- 例: + ```toml + [libraries."libnyash_filebox_plugin.so".FileBox.methods] + copyFrom = { method_id = 7, args = [ { kind = "box", category = "plugin" } ] } + cloneSelf = { method_id = 8 } + ``` +- ローダは `args` の型宣言に基づいて実引数を検証。型不一致は `InvalidArgs`。 + +## 3. 返り値(v2.2) +- プラグインが `tag=8` を返した場合、ローダは `type_id` を `nyash.toml` で逆引きし、 + `PluginBoxV2 { box_type, type_id, invoke_fn, instance_id, fini_method_id }` を構築して返す。 + +## 4. VM/インタプリタの扱い +- メソッド呼び出しはローダ経由に統一(TLV/Handle処理もローダ側)。 +- MIR Loweringは以下を厳守: + - ユーザー定義Boxのみ関数化(Call)最適化可。 + - プラグイン/ビルトインは常に `BoxCall` を出す(VMでローダに委譲)。 +- VMのBoxRefは共有ハンドルとして扱う: + - `clone_box()` ではなく `share_box()` を使用(不意のbirth回避)。 + +## 5. プラグイン実装の注意 +- `open` のモードによっては `read` ができない(例: "w")。`copyFrom` は `file.read` が失敗したら `buffer` にフォールバックする。 +- `write` 実装では、成功後に `buffer` を更新しておくと `copyFrom` のフォールバックで活きる。 +- 典型メソッドID(例: FileBox) + - `0=birth`, `1=open`, `2=read`, `3=write`, `4=close`, `0xFFFFFFFF=fini`, `7=copyFrom`, `8=cloneSelf` + +## 6. トラブルシュート +- rc=-4 `Invalid arguments` + - `args` 型宣言と実引数が不一致。ローダログの引数エンコードを確認(String化フォールバックが出ていないか)。 +- rc=-5 `Plugin internal error` + - プラグイン内部のread/write/lock失敗など。`copyFrom` のfile→bufferフォールバック不備を疑う。 +- rc=-8 `Invalid handle` + - 存在しない `instance_id` に対する呼び出し。VMで `clone_box` を使っていないか(`share_box` へ)。 + +## 7. 参考 +- 仕様: `docs/reference/plugin-system/nyash-toml-v2_1-spec.md` +- 実装: `src/runtime/plugin_loader_v2.rs`(引数検証/Handle戻り値復元) +- 例: `docs/examples/plugin_boxref_return.nyash` + diff --git a/mir_error.txt b/mir_error.txt new file mode 100644 index 00000000..0cae45e9 --- /dev/null +++ b/mir_error.txt @@ -0,0 +1,57 @@ +🔍 DEBUG: Initializing v2 plugin system +[FileBox] Plugin initialized +🔌 v2 plugin system initialized from nyash.toml + 📦 Registering plugin provider for FileBox +✅ v2 plugin system fully configured +🚀 Nyash MIR Compiler - Processing file: docs/examples/visibility_error.nyash 🚀 +🚀 MIR Output for docs/examples/visibility_error.nyash: +; MIR Module: main + +define void @User.birth/2(box %0, ? %1, ? %2) effects(read) { +bb1: + 0: ref_set %0.name = %1 + 1: ref_set %0.age = %2 + 2: %3 = const void + 3: ret %3 +} + +define void @main() { +bb0: + 0: safepoint + 1: %0 = const "__me__" + 2: %1 = new ConsoleBox() + 3: call %1.birth() + 4: ref_set %0.console = %1 + 5: %2 = const "__box_type_User" + 6: %3 = const "__field_User_age" + 7: %4 = const "__field_User_passwordHash" + 8: %5 = const "__field_User_name" + 9: %6 = const void + 10: %7 = const "Alice" + 11: %8 = new StringBox(%7) + 12: call %8.birth(%7) + 13: %9 = const 20 + 14: %10 = new IntegerBox(%9) + 15: call %10.birth(%9) + 16: %11 = new User(%8, %10) + 17: call %11.birth(%8, %10) + 18: %12 = const "__me__" + 19: %13 = ref_get %12.console + 20: %14 = const "name(public)=" + 21: %15 = new StringBox(%14) + 22: call %15.birth(%14) + 23: %16 = ref_get %11.name + 24: %17 = %15 Add %16 + 25: %18 = call %13.log(%17) + 26: %19 = const 30 + 27: %20 = new IntegerBox(%19) + 28: call %20.birth(%19) + 29: ref_set %11.age = %20 + 30: %21 = const "__me__" + 31: %22 = ref_get %21.console + 32: %23 = ref_get %11.age + 33: %24 = call %22.log(%23) + 34: ret %24 +} + + diff --git a/mir_ok.txt b/mir_ok.txt new file mode 100644 index 00000000..64f5a7d0 --- /dev/null +++ b/mir_ok.txt @@ -0,0 +1,91 @@ +🔍 DEBUG: Initializing v2 plugin system +[FileBox] Plugin initialized +🔌 v2 plugin system initialized from nyash.toml + 📦 Registering plugin provider for FileBox +✅ v2 plugin system fully configured +🚀 Nyash MIR Compiler - Processing file: docs/examples/visibility_ok.nyash 🚀 +🚀 MIR Output for docs/examples/visibility_ok.nyash: +; MIR Module: main + +define void @User.birth/2(box %0, ? %1, ? %2) effects(read) { +bb1: + 0: ref_set %0.name = %1 + 1: ref_set %0.age = %2 + 2: %3 = const void + 3: ret %3 +} + +define void @User.setAge/1(box %0, ? %1) effects(read) { +bb3: + 0: ref_set %0.age = %1 + 1: %2 = const void + 2: ret %2 +} + +define ? @User.getAge/0(box %0) effects(read) { +bb2: + 0: %1 = ref_get %0.age + 1: ret %1 +} + +define void @main() { +bb0: + 0: safepoint + 1: %0 = const "__me__" + 2: %1 = new ConsoleBox() + 3: call %1.birth() + 4: ref_set %0.console = %1 + 5: %2 = const "__box_type_User" + 6: %3 = const "__field_User_age" + 7: %4 = const "__field_User_passwordHash" + 8: %5 = const "__field_User_name" + 9: %6 = const "__method_User_getAge" + 10: %7 = const "__method_User_setAge" + 11: %8 = const void + 12: %9 = const "Alice" + 13: %10 = new StringBox(%9) + 14: call %10.birth(%9) + 15: %11 = const 20 + 16: %12 = new IntegerBox(%11) + 17: call %12.birth(%11) + 18: %13 = new User(%10, %12) + 19: call %13.birth(%10, %12) + 20: %14 = const "__me__" + 21: %15 = ref_get %14.console + 22: %16 = const "name(public)=" + 23: %17 = new StringBox(%16) + 24: call %17.birth(%16) + 25: %18 = ref_get %13.name + 26: %19 = %17 Add %18 + 27: %20 = call %15.log(%19) + 28: %21 = const "Bob" + 29: %22 = new StringBox(%21) + 30: call %22.birth(%21) + 31: ref_set %13.name = %22 + 32: %23 = const "__me__" + 33: %24 = ref_get %23.console + 34: %25 = const "age(private, internal)=" + 35: %26 = new StringBox(%25) + 36: call %26.birth(%25) + 37: %28 = const "User.getAge/0" + 38: %27 = call %28(%13) + 39: %29 = %26 Add %27 + 40: %30 = call %24.log(%29) + 41: %31 = const 21 + 42: %32 = new IntegerBox(%31) + 43: call %32.birth(%31) + 44: %33 = const 21 + 45: %34 = new IntegerBox(%33) + 46: call %34.birth(%33) + 47: %36 = const "User.setAge/1" + 48: %35 = call %36(%13, %34) + 49: %37 = const "__me__" + 50: %38 = ref_get %37.console + 51: %39 = const "done" + 52: %40 = new StringBox(%39) + 53: call %40.birth(%39) + 54: %41 = call %38.log(%40) + 55: ret %41 +} + + diff --git a/src/backend/vm.rs b/src/backend/vm.rs index 1fe243bd..bca1184d 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -175,6 +175,8 @@ pub struct VM { object_fields: HashMap>, /// Class name mapping for objects (for visibility checks) object_class: HashMap, + /// Marks ValueIds that represent internal (me/this) references within the current function + object_internal: std::collections::HashSet, /// Loop executor for handling phi nodes and loop-specific logic loop_executor: LoopExecutor, /// Shared runtime for box creation and declarations @@ -208,6 +210,7 @@ impl VM { last_result: None, object_fields: HashMap::new(), object_class: HashMap::new(), + object_internal: std::collections::HashSet::new(), loop_executor: LoopExecutor::new(), runtime: NyashRuntime::new(), scope_tracker: ScopeTracker::new(), @@ -232,6 +235,7 @@ impl VM { last_result: None, object_fields: HashMap::new(), object_class: HashMap::new(), + object_internal: std::collections::HashSet::new(), loop_executor: LoopExecutor::new(), runtime, scope_tracker: ScopeTracker::new(), @@ -300,6 +304,16 @@ impl VM { } } + // Heuristic: map `me` (first param) to class name parsed from function name (e.g., User.method/N) + if let Some(first) = function.params.get(0) { + if let Some((class_part, _rest)) = func_name.split_once('.') { + // Record class for internal field visibility checks + self.object_class.insert(*first, class_part.to_string()); + // Mark internal reference + self.object_internal.insert(*first); + } + } + // Execute the function let result = self.execute_function(&function); @@ -657,6 +671,14 @@ impl VM { // Copy instruction - duplicate the source value let val = self.get_value(*src)?; self.set_value(*dst, val); + // Propagate class mapping for references (helps track `me` copies) + if let Some(class_name) = self.object_class.get(src).cloned() { + self.object_class.insert(*dst, class_name); + } + // Propagate internal marker (me/this lineage) + if self.object_internal.contains(src) { + self.object_internal.insert(*dst); + } Ok(ControlFlow::Continue) }, @@ -702,13 +724,16 @@ impl VM { }, MirInstruction::RefGet { dst, reference, field } => { - // Visibility check (if class known and visibility declared) - if let Some(class_name) = self.object_class.get(reference) { - if let Ok(decls) = self.runtime.box_declarations.read() { - if let Some(decl) = decls.get(class_name) { - let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); - if has_vis && !decl.public_fields.contains(field) { - return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + // Visibility check (if class known and visibility declared). Skip for internal refs. + let is_internal = self.object_internal.contains(reference); + if !is_internal { + if let Some(class_name) = self.object_class.get(reference) { + if let Ok(decls) = self.runtime.box_declarations.read() { + if let Some(decl) = decls.get(class_name) { + let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); + if has_vis && !decl.public_fields.contains(field) { + return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + } } } } @@ -733,13 +758,16 @@ impl VM { MirInstruction::RefSet { reference, field, value } => { // Get the value to set let new_value = self.get_value(*value)?; - // Visibility check (treat all RefSet as external writes) - if let Some(class_name) = self.object_class.get(reference) { - if let Ok(decls) = self.runtime.box_declarations.read() { - if let Some(decl) = decls.get(class_name) { - let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); - if has_vis && !decl.public_fields.contains(field) { - return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + // Visibility check (Skip for internal refs; otherwise enforce public) + let is_internal = self.object_internal.contains(reference); + if !is_internal { + if let Some(class_name) = self.object_class.get(reference) { + if let Ok(decls) = self.runtime.box_declarations.read() { + if let Some(decl) = decls.get(class_name) { + let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); + if has_vis && !decl.public_fields.contains(field) { + return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + } } } }