feat: Complete internal field access identification for visibility system
- Add internal access tracking to distinguish me.field from external.field access - Interpreter correctly allows methods to access their own private fields - VM tracks object class context during method calls for visibility checks - Fix VM nested box declaration collection in collect_box_declarations - Both interpreter and VM now pass all visibility tests consistently Test results: - visibility_ok.nyash: ✅ Internal private access allowed in methods - visibility_error.nyash: ✅ External private access correctly blocked - All private fields accessible from within their own methods - Public fields remain accessible from anywhere 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
64
docs/reference/language/field-visibility-and-delegation.md
Normal file
64
docs/reference/language/field-visibility-and-delegation.md
Normal file
@ -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`
|
||||
|
||||
53
docs/reference/plugin-system/boxref-behavior.md
Normal file
53
docs/reference/plugin-system/boxref-behavior.md
Normal file
@ -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`
|
||||
|
||||
57
mir_error.txt
Normal file
57
mir_error.txt
Normal file
@ -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<User> %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
|
||||
}
|
||||
|
||||
|
||||
91
mir_ok.txt
Normal file
91
mir_ok.txt
Normal file
@ -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<User> %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<User> %0, ? %1) effects(read) {
|
||||
bb3:
|
||||
0: ref_set %0.age = %1
|
||||
1: %2 = const void
|
||||
2: ret %2
|
||||
}
|
||||
|
||||
define ? @User.getAge/0(box<User> %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
|
||||
}
|
||||
|
||||
|
||||
@ -175,6 +175,8 @@ pub struct VM {
|
||||
object_fields: HashMap<ValueId, HashMap<String, VMValue>>,
|
||||
/// Class name mapping for objects (for visibility checks)
|
||||
object_class: HashMap<ValueId, String>,
|
||||
/// Marks ValueIds that represent internal (me/this) references within the current function
|
||||
object_internal: std::collections::HashSet<ValueId>,
|
||||
/// 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,7 +724,9 @@ impl VM {
|
||||
},
|
||||
|
||||
MirInstruction::RefGet { dst, reference, field } => {
|
||||
// Visibility check (if class known and visibility declared)
|
||||
// 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) {
|
||||
@ -713,6 +737,7 @@ impl VM {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get field value from object
|
||||
let field_value = if let Some(fields) = self.object_fields.get(reference) {
|
||||
if let Some(value) = fields.get(field) {
|
||||
@ -733,7 +758,9 @@ 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)
|
||||
// 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) {
|
||||
@ -744,6 +771,7 @@ impl VM {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure object has field storage
|
||||
if !self.object_fields.contains_key(reference) {
|
||||
|
||||
Reference in New Issue
Block a user