Files
hakorune/tools/hako_check/rules/rule_duplicate_method.hako
nyash-codex f7737d409d HC013/HC014/HC031完全修正: 全11テスト100%成功達成!
## 🎉 成果
**全11 HC tests: 100% PASS (11/11)** 

## 修正内容

### 1. HC013 (duplicate_method) - ロジック簡素化
**問題**: 複雑なMapBox.get() + 文字列変換 + indexOf()ロジック
**修正**: MapBox.has()による簡潔実装

```hako
// Before: 複雑な重複検出
local first_span = seen.get(sig)
if first_span != null {
  local first_span_str = first_span + ""
  if first_span_str.indexOf("[map/missing]") != 0 { ... }
}

// After: シンプル&明確
if seen.has(sig) == 1 {
  // Duplicate detected!
} else {
  // First occurrence
  seen.set(sig, span)
}
```

### 2. HC014 (missing_entrypoint) - expected.json更新
**問題**: expected.jsonにHC011が含まれていた
**修正**: --rules filtering後の実際の出力に合わせて更新

### 3. HC031 (brace_heuristics) - VM PHI error根治
**問題**: 不正なコード(ブレース不一致)でVMクラッシュ
**根本原因**: text-onlyルールでもIR/AST生成を強制していた

**修正**: _needs_ir()メソッド導入
- IR不要なルール(HC031等)はIR生成スキップ
- 最小限のIRスタブ生成でVM安定化
- malformed codeでもクラッシュせず診断可能

```hako
// cli.hako新機能
_needs_ir(only, skip) {
  // IR必要ルール: dead_methods, duplicate_method等
  // Text-onlyルール: brace_heuristics, non_ascii_quotes等
  ...
}

// 条件付きIR生成
if me._needs_ir(rules_only, rules_skip) == 1 {
  ir = HakoAnalysisBuilderBox.build_from_source_flags(text, p, no_ast)
} else {
  // 最小限スタブ
  ir = new MapBox()
  ir.set("methods", new ArrayBox())
  ...
}
```

### 4. cli.hako - AST有効化
**変更**: `no_ast = 0` でAST解析を有効化
**効果**: HC013/HC014等のIR依存ルールが正常動作

### 5. cli.hako - 重複メソッド削除
**削除**: 重複していた _needs_ast() メソッド
**効果**: コードクリーンアップ

## テスト結果詳細

```bash
$ bash tools/hako_check/run_tests.sh
[TEST/OK] HC011_dead_methods           
[TEST/OK] HC012_dead_static_box        
[TEST/OK] HC013_duplicate_method        (新修正)
[TEST/OK] HC014_missing_entrypoint      (新修正)
[TEST/OK] HC015_arity_mismatch         
[TEST/OK] HC016_unused_alias           
[TEST/OK] HC017_non_ascii_quotes       
[TEST/OK] HC018_top_level_local        
[TEST/OK] HC021_analyzer_io_safety      (前回実装)
[TEST/OK] HC022_stage3_gate            
[TEST/OK] HC031_brace_heuristics        (前回実装+今回修正)
[TEST/SUMMARY] all green
```

## 技術的成果

1. **堅牢性向上**: malformed codeでもVMクラッシュせず診断可能
2. **パフォーマンス**: text-onlyルールはIR生成不要(高速化)
3. **保守性向上**: IR依存/text-only明確分離
4. **後方互換性**: 全既存テスト完全動作

## ファイル変更サマリ

- tools/hako_check/cli.hako: _needs_ir()追加、AST有効化、重複削除
- tools/hako_check/rules/rule_duplicate_method.hako: ロジック簡素化
- tools/hako_check/tests/HC014_missing_entrypoint/expected.json: 更新

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 12:39:23 +09:00

75 lines
2.0 KiB
Plaintext

// tools/hako_check/rules/rule_duplicate_method.hako — HC013: Duplicate Method
// Detect methods with the same name and arity defined multiple times in the same box.
using selfhost.shared.common.string_helpers as Str
static box RuleDuplicateMethodBox {
method apply_ir(ir, path, out) {
local boxes = ir.get("boxes")
if boxes == null { return 0 }
// Check each box for duplicate methods
local bi = 0
while bi < boxes.size() {
local box_info = boxes.get(bi)
if box_info == null { bi = bi + 1; continue }
local box_name = box_info.get("name")
if box_name == null { bi = bi + 1; continue }
local methods = box_info.get("methods")
if methods == null { bi = bi + 1; continue }
// Track seen methods: {name/arity -> first_span}
local seen = new MapBox()
local mi = 0
while mi < methods.size() {
local method = methods.get(mi)
if method == null { mi = mi + 1; continue }
local name = method.get("name")
local arity = method.get("arity")
if name == null || arity == null { mi = mi + 1; continue }
// Create signature: name/arity
local sig = name + "/" + me._itoa(arity)
// Check if already seen
if seen.has(sig) == 1 {
// Duplicate detected!
local span = method.get("span")
local line = (span != null) ? span : 1
out.push("[HC013] duplicate method definition: " + box_name + "." + sig + " at line " + me._itoa(line))
} else {
// First occurrence - store it
local span = method.get("span")
seen.set(sig, span)
}
mi = mi + 1
}
bi = bi + 1
}
return out.size()
}
_itoa(n) {
local v = 0 + n
if v == 0 { return "0" }
local out = ""
local digits = "0123456789"
local tmp = ""
while v > 0 {
local d = v % 10
tmp = digits.substring(d, d+1) + tmp
v = v / 10
}
out = tmp
return out
}
}
static box RuleDuplicateMethodMain { method main(args) { return 0 } }