# 機能設計書 35-型不一致 (assign-type-mismatch)

## 概要

本ドキュメントは、Lua Language Serverにおける「型不一致検出機能」の設計詳細を記載したものである。変数への代入時に、宣言された型と代入される値の型が一致しない場合を検出し、型安全性の向上を支援する診断機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：
Luaは動的型付け言語だが、LuaDocアノテーションにより型情報を付与できる。型アノテーションを使用している場合、宣言された型と実際に代入される値の型が一致しないケースは潜在的なバグの原因となる。本機能は、代入時の型不一致を検出し、開発者に警告することで、型安全なコードの記述を支援する。

**機能の利用シーン**：
- 型アノテーション付きのLuaコードの記述時
- 既存コードへの型アノテーション追加時
- リファクタリング時の型整合性チェック

**主要な処理内容**：
1. 対象ファイルのASTを取得する
2. 代入操作（local/setlocal/setglobal/setfield/setindex/setmethod/tablefield/tableindex/tableexp）を走査する
3. 変数の宣言型（varNode）と代入値の型（valueNode）をコンパイルする
4. `vm.canCastType`で型の互換性をチェックする
5. 型不一致の場合、詳細なエラーメッセージを生成して報告する

**関連システム・外部連携**：
- LSP (Language Server Protocol)クライアント（VSCode等のエディタ）との連携
- 診断結果はLSPの`textDocument/publishDiagnostics`通知で送信される

**権限による制御**：
- 設定`Lua.diagnostics.disable`で本診断を無効化可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | 診断情報 | 補助機能 | 型の不一致を検出 |

## 機能種別

バリデーション / 静的解析 / 型チェック / 診断

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| uri | string | Yes | 診断対象ファイルのURI | 有効なファイルURIであること |
| callback | function | Yes | 診断結果を受け取るコールバック関数 | - |

### 入力データソース

- ファイルシステムから読み込まれたLuaソースコード
- `files.getState(uri)`から取得されるAST情報
- LuaDocアノテーションによる型情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| start | integer | エラー位置の開始オフセット |
| finish | integer | エラー位置の終了オフセット |
| message | string | 診断メッセージ（期待される型と実際の型を含む） |

### 出力先

- LSPクライアントへの診断通知（`textDocument/publishDiagnostics`）
- エディタ上でのインライン警告表示

## 処理フロー

### 処理シーケンス

```
1. ファイル状態の取得
   └─ files.getState(uri)でAST付きの状態を取得
2. 代入操作ノードの走査
   └─ guide.eachSourceTypes(state.ast, checkTypes, callback)
3. 値の存在チェック
   └─ source.valueがない場合はスキップ
4. setlocalの特殊処理
   └─ locNode.hasDefinedがない場合はスキップ
5. nil代入の特殊処理
   └─ hasMarkType(source)がある場合は許可
6. 型ノードのコンパイル
   └─ vm.compileNode(value)とvm.compileNode(source)
7. 型互換性チェック
   └─ vm.canCastType(uri, varNode, valueNode, errs)
8. クラスマーク時の逆変性チェック
   └─ hasMarkClass(source)の場合の追加チェック
9. 診断結果の報告
   └─ 型情報とエラー詳細を含むメッセージ
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[ファイル状態を取得]
    B --> C{状態が存在するか?}
    C -->|No| D[終了]
    C -->|Yes| E[代入操作ノードを走査]
    E --> F{valueがあるか?}
    F -->|No| G[次のノードへ]
    F -->|Yes| H[delayer.delay]
    H --> I{setlocalか?}
    I -->|Yes| J{hasDefinedか?}
    J -->|No| G
    J -->|Yes| K[value.type判定]
    I -->|No| K
    K --> L{value == nil?}
    L -->|Yes| M{hasMarkTypeか?}
    M -->|Yes| G
    M -->|No| N[setfield/setindex?]
    N -->|Yes| G
    N -->|No| O[型コンパイル]
    L -->|No| O
    O --> P{canCastType?}
    P -->|Yes| G
    P -->|No| Q{hasMarkClassか?}
    Q -->|Yes| R{逆変性OK?}
    R -->|Yes| G
    R -->|No| S[診断報告]
    Q -->|No| S
    S --> G
    G --> T{全ノード完了?}
    T -->|No| E
    T -->|Yes| D
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-35-01 | 対象操作タイプ | local/setlocal/setglobal/setfield/setindex/setmethod/tablefield/tableindex/tableexp | checkTypes配列 |
| BR-35-02 | setlocal制限 | setlocalは変数にhasDefinedがある場合のみ対象 | 型が明示的に定義されている変数 |
| BR-35-03 | nil代入許可 | hasMarkType(doc.type/doc.class)がある場合のnil代入は許可 | 型アノテーション付き変数 |
| BR-35-04 | フィールドnil代入 | setfield/setindexへのnil代入は許可 | フィールド初期化パターン |
| BR-35-05 | optional削除 | setindex/tableexpではvalueNodeからoptionalを削除 | boolean[1] = nilパターン |
| BR-35-06 | truthy設定 | getfield/getindexではvalueNodeをtruthyに設定 | 型収窄不可のため |
| BR-35-07 | 逆変性許可 | hasMarkClassの場合、逆方向の型キャストも許可 | setmetatable継承パターン |

### 計算ロジック

#### 型互換性判定

```lua
-- varNode: 変数の宣言型
-- valueNode: 代入値の型
-- errs: エラー詳細を格納する配列

if vm.canCastType(uri, varNode, valueNode, errs) then
    return -- 互換性あり
end

-- hasMarkClassの場合の逆変性チェック
if hasMarkClass(source) then
    if vm.canCastType(uri, valueNode:copy():remove 'table', varNode) then
        return -- 逆変性OK
    end
end

-- 診断報告
callback {
    message = lang.script('DIAG_ASSIGN_TYPE_MISMATCH', {
        def = vm.getInfer(varNode):view(uri),
        ref = vm.getInfer(valueNode):view(uri),
    }) .. '\n' .. vm.viewTypeErrorMessage(uri, errs),
}
```

## データベース操作仕様

本機能はデータベースを使用しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ファイル状態なし | files.getState(uri)がnilを返す場合 | 処理を中断して正常終了 |

### リトライ仕様

リトライは行わない。ファイルが再度編集された際に自動的に再診断される。

## トランザクション仕様

該当なし（読み取り専用処理）

## パフォーマンス要件

- 単一ファイルの診断は通常0.5秒以内に完了すること
- `await.newThrottledDelayer(15)`によりスロットリングを適用

## セキュリティ考慮事項

- ユーザーコードの読み取りのみを行い、ファイルシステムへの書き込みは行わない
- 外部ネットワークへのアクセスは行わない

## 備考

- LuaDocアノテーション（`---@type`、`---@class`など）が付与されている変数が主な対象
- 型推論により暗黙的に型が決定される場合も診断対象となる

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | vm/infer.lua | `script/vm/infer.lua` | vm.infer型、view関数 |
| 1-2 | vm/node.lua | `script/vm/node.lua` | vm.nodeの構造、copy/remove/setTruthy |

**読解のコツ**: vm.compileNodeは型情報を「ノード」として返す。ノードにはcopy/remove/setTruthyなどのメソッドがある。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | assign-type-mismatch.lua | `script/core/diagnostics/assign-type-mismatch.lua` | メイン診断関数 |

**主要処理フロー**:
1. **7-17行目**: checkTypes配列で対象操作タイプを定義
2. **21-32行目**: hasMarkType関数 - doc.type/doc.classバインドチェック
3. **36-46行目**: hasMarkClass関数 - doc.classバインドチェック
4. **49-53行目**: ファイル状態取得
5. **55行目**: await.newThrottledDelayer(15)でスロットリング
6. **57-120行目**: メインループ

#### Step 3: 型判定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | assign-type-mismatch.lua | `script/core/diagnostics/assign-type-mismatch.lua` | 型互換性判定部分 |

**主要処理フロー**:
- **63-68行目**: setlocalのhasDefined制限
- **69-83行目**: nil代入の特殊処理
- **85-97行目**: 型ノードのコンパイルと調整
- **99-103行目**: vm.canCastTypeで互換性チェック
- **105-110行目**: hasMarkClassの逆変性チェック
- **112-119行目**: 診断メッセージ生成

#### Step 4: VMの型システムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | vm/type.lua | `script/vm/type.lua` | canCastType, viewTypeErrorMessage |
| 4-2 | vm/infer.lua | `script/vm/infer.lua` | getInfer, view |

### プログラム呼び出し階層図

```
script/core/diagnostics/init.lua (診断エントリー)
    │
    ├─ require('core.diagnostics.assign-type-mismatch')
    │      │
    │      ├─ files.getState(uri)
    │      │
    │      ├─ await.newThrottledDelayer(15)
    │      │
    │      ├─ guide.eachSourceTypes(state.ast, checkTypes, callback)
    │      │      │
    │      │      ├─ hasMarkType(source)
    │      │      ├─ hasMarkClass(source)
    │      │      │
    │      │      ├─ vm.compileNode(value)
    │      │      │      └─ valueNode:copy():removeOptional()
    │      │      │      └─ valueNode:copy():setTruthy()
    │      │      │
    │      │      ├─ vm.compileNode(source)
    │      │      │
    │      │      └─ vm.canCastType(uri, varNode, valueNode, errs)
    │      │
    │      └─ callback(result)
    │             │
    │             ├─ vm.getInfer(varNode):view(uri)
    │             ├─ vm.getInfer(valueNode):view(uri)
    │             └─ vm.viewTypeErrorMessage(uri, errs)
    │
    └─ LSP診断通知
```

### データフロー図

```
[入力]                     [処理]                           [出力]

Luaソースコード ───▶ パーサー(AST生成) ───▶ AST
      +                                     │
LuaDocアノテーション                         ▼
                    guide.eachSourceTypes(checkTypes)
                                              │
                                              ▼
                    vm.compileNode(value) ───▶ valueNode
                    vm.compileNode(source) ───▶ varNode
                                              │
                                              ▼
                    vm.canCastType(varNode, valueNode)
                                              │
                                              ▼
                    ┌──────────────────────────┴──────────────────────────┐
                    │                                                      │
                    ▼                                                      ▼
              互換性あり ───▶ スキップ                               互換性なし
                                                                          │
                                                                          ▼
                                                    vm.getInfer().view() ───▶ 型名表示
                                                    vm.viewTypeErrorMessage() ───▶ 詳細
                                                                          │
                                                                          ▼
                                                                    LSP診断通知
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| assign-type-mismatch.lua | `script/core/diagnostics/assign-type-mismatch.lua` | ソース | 型不一致診断のメインロジック |
| init.lua | `script/core/diagnostics/init.lua` | ソース | 診断処理のエントリーポイント |
| vm/type.lua | `script/vm/type.lua` | ソース | canCastType、型互換性チェック |
| vm/infer.lua | `script/vm/infer.lua` | ソース | 型推論、view表示 |
| vm/node.lua | `script/vm/node.lua` | ソース | 型ノード操作 |
| guide.lua | `script/parser/guide.lua` | ソース | AST操作 |
| files.lua | `script/files.lua` | ソース | ファイル状態管理 |
| await.lua | `script/await.lua` | ソース | スロットリング、非同期処理 |
| language.lua | `script/language.lua` | ソース | 診断メッセージ定義 |
