mie LSP サーバ (mie lsp) 仕様
本書は mie lsp の仕様を定める。起動ディスパッチ全体は mie-command.md、言語の意味論は language-spec.md、静的検査は static-analysis.md を参照。
stdio 上で Language Server Protocol 3.x をしゃべる。vscode-mie 拡張から spawn されることを主用途とする。
起動
$ mie lsp [--strict]
stdin/stdout で JSON-RPC をやりとりする。ログは stderr に出る。--strict を付けると診断が strict モード で動き、健全・漸進検査 (mie-command.md §6.2) に strict 規則 (static-analysis.md §3) を上乗せする (追加検出は mie-command.md §6.3 と同じ 2 点)。フラグなしの既定は非 strict。vscode-mie 拡張の mie.strict 設定 (既定 false) が ON のとき、拡張はこのフラグ付きで LSP を spawn する。
--strict が無くても、診断対象ドキュメントが header #{mie strict} / #[mie strict] (language-spec.md §13.1) を宣言していれば、そのドキュメントは strict 診断になる (実効 strict = 「--strict」OR「ドキュメント header の strict 属性」、診断)。これにより vscode-mie の mie.strict が false でも、strict を宣言したファイルだけは strict として検査される。
プロトコル
- トランスポート:
Content-Length:ヘッダ付き JSON-RPC 2.0 - 起動シーケンス:
initialize→initialized→ 通常運用 →shutdown→exit
capabilities
initialize で返す主な capabilities:
| 機能 | 値 |
|---|---|
textDocumentSync | 1 (Full sync) |
semanticTokensProvider.legend | defaultLegend (highlight pkg 由来) |
semanticTokensProvider.full | true |
definitionProvider | true |
referencesProvider | true |
hoverProvider | true |
completionProvider.triggerCharacters | ["."] |
documentFormattingProvider | true |
codeActionProvider.codeActionKinds | ["quickfix", "source.fixAll"] |
documentSymbolProvider | true |
renameProvider.prepareProvider | true |
inlayHintProvider | true |
documentHighlightProvider | true |
serverInfo.name | "mie-lsp" |
サポート method
| method | 種別 | 説明 |
|---|---|---|
initialize | request | capabilities 返却 |
shutdown | request | shutdown 受領フラグを立てて null 応答 |
textDocument/semanticTokens/full | request | ハイライト用 token 配列を返す |
textDocument/definition | request | 定義位置 (go-to-definition) を返す (定義ジャンプ) |
textDocument/references | request | 識別子の参照箇所一覧を返す |
textDocument/hover | request | カーソル下の識別子の型情報を返す (hover) |
textDocument/completion | request | ドット直後のメンバ補完候補を返す (補完) |
textDocument/formatting | request | ドキュメント全体を整形した全文置換 TextEdit を返す (整形) |
textDocument/codeAction | request | lint 自動修正の quickfix / source.fixAll を返す (コードアクション) |
textDocument/documentSymbol | request | ファイル内のトップレベル宣言を階層シンボルで返す (シンボル一覧) |
textDocument/prepareRename | request | カーソル位置がリネーム可能なら対象範囲を返す (リネーム) |
textDocument/rename | request | シンボルの全出現を新名へ置換する WorkspaceEdit を返す (リネーム) |
textDocument/inlayHint | request | 型注釈なし束縛に推論型のインラインヒントを返す (インレイヒント) |
textDocument/documentHighlight | request | カーソル下シンボルの同名出現を返す (出現ハイライト) |
mie/internalSource | request | 処理系埋め込みソース (prelude.mie 等) の仮想ドキュメント本文を返す (定義ジャンプ) |
initialized | notification | no-op |
exit | notification | プロセス終了 (shutdown 後ならクリーン) |
textDocument/didOpen | notification | ドキュメント登録 |
textDocument/didChange | notification | Full sync で本文置換 |
textDocument/didClose | notification | ドキュメント破棄 |
$/cancelRequest | notification | no-op (semanticTokens/full は同期完結) |
未対応 method の request にはエラー (-32601 MethodNotFound) を返す。未対応 notification は無視する。
編集途中ソースの扱い
semantic tokens のためにパースする際は、通常モードで失敗したら SkipHeader() でリトライする。#{mie} は省略できるため通常はリトライ不要だが、#[mie] を打ち途中のソース等でもハイライトが切れないようにする。
ファイル名が _test.mie で終わるドキュメントでは、mie test がファイルスコープに供給する test / suite (std:test) と assert (std:assert) (mie-command.md §7.2 / §7.3) を、@import 無しでも builtin として意味分類する (mie test 文脈に限った着色)。通常ファイルでは着色せず、これらは @import 束縛越しに標準ライブラリメンバーとして扱う。
診断
textDocument/didOpen / textDocument/didChange を受けると、当該ドキュメントを entry とした静的検査 (mie-command.md §6 / static-analysis.md §1) を実行し、その結果をサーバ → クライアントの push 通知 textDocument/publishDiagnostics で送る。検査がクリーンなら空配列を送って既存マーカを消す。import 先 (別ファイル) の診断は、そのファイル自身を開いたときの検査で出るため、push 対象は当該ドキュメントに属する診断のみに絞る。健全・漸進検査がクリーンなドキュメントに対して strict 規則 (mie-command.md §6.3) を追加適用するのは、--strict (起動) 付きで起動したとき、または そのドキュメントが header #{mie strict} / #[mie strict] を宣言しているとき (実効 strict = 「--strict」OR「ドキュメント header の strict 属性」)。strict 診断も push する。
静的検査 (Error) に加え、パースが通るドキュメントには mie lint (mie-command.md §9) の所見も Warning 診断として同じ push に含める (source は mie-lint、code はルール名 — unused / self-assign / control-juxtaposition / non-tail-recur)。構文エラーがあると lint はスキップする (構文エラーは静的検査が Error で出す)。自動修正可能な所見は code action (コードアクション) で修正できる。
hover
textDocument/hover は、カーソル下の識別子の型情報を Markdown (MarkupContent) で返す。対象を次の系統に分けて解決する:
- prelude 組込名 (再束縛されていない
print/if/range/Some等): ユーザ定義識別子と揃えて先頭に「型」(name: 型)、続けて「形 + 一行の意味」を出す (例:print: {Any | Unit}とprint(s)と「文字列 s を改行付きで出力する。」)。情報源は prelude が持つシグネチャ表で、root 常在束縛する flat 名 (prelude.md 各表の「形」「意味」列) を網羅する。型は関数組込が mie 型表記 ({引数 | 結果}) の手書き、値束縛 (reflect/None/Less/Equal/Greater) は実値の反射 (reflect.type_of相当) で導出する。 - **標準ライブラリ (
std:*) のメンバー**:math := @import "std:math"のような束縛越しのmath.sqrt、[read, write] := @import "std:fs"のような分解束縛名 (read) の上で、prelude と揃えた「型 + 形 + 一行の意味」を出す (例:math.sqrt: {Float | Float}とmath.sqrt(x)と「平方根 (Float)。…」)。さらに@import "std:<name>"を束縛した識別子自体 (math) の上ではモジュール概要を出す。情報源は std の各モジュール文書 (std/<name>.md 各表の「形」「意味」列) を基にした手書きのシグネチャ表で、各モジュール直下スロットを網羅する (サブオブジェクトのメソッド —timeのInstant/Durationやrandomジェネレータのint/float等 — は将来対応)。 - ユーザ定義識別子 (束縛・スロット・その参照): 静的型検査 (mie-command.md §6) と同じ推論で得た型を
name: 型で出す (例:x: Int)。型が確定しない (Unknown) 位置では何も返さない。 - 型メソッド (prelude method, dot プロパティ) (
xs.map/s.length/o.unwrap等): レシーバ式の静的型を mie-command.md §6 と同じ推論で求め、確定するときその推論型を先頭行に、続けて「形 + 一行の意味」を出す (例:[1, 2, 3].mapのmap上でList(Int)とxs.map(block)と意味)。情報源は prelude が持つ型メソッドのシグネチャ表で、原始型ごとのメソッド (prelude.md 各型メソッド表の「形」「意味」列) を網羅する。受け手の型に依らない universal method (compare/matches/inspect) も対象。レシーバ型が確定しない位置では何も返さない。 - ユーザ定義オブジェクトのメンバ (
p.x等の dot スロット): レシーバが定義オブジェクトに帰着するとき、そのスロットの推論型をprop: 型で出す (import 越しも解決する)。 @type定義名とその参照 (@type Text := …のText、および型注釈位置の参照x: Text/e: TextのText等): その定義を@type Name := <基底型>で出す。ここでは別名Nameを保たず基底(展開)型を見せる — 「型の中身(定義)を示す」のが目的のため (例:@type Text := {kind: String, s: String |})。@type Key := Idのように右辺がさらに別の@type名なら、その参照名 (Id) で出す。- テストライブラリ組込 (
test/suite/assert、mie-command.md §7.2): ファイル名が_test.mieで終わるドキュメントに限り、mie testがファイルスコープに供給する束縛 (mie-command.md §7.3) として、prelude 組込名と揃えた「型 + 形 + 一行の意味」を出す (test/suiteはフラット名、assertは namespace 概要、assert.equal等のメンバーはassert.<member>: 型)。通常ファイルでは@import "std:test"/"std:assert"束縛越しに標準ライブラリメンバー (上記) として扱う。ユーザがこれらの名を再束縛している場合はユーザ定義識別子として扱う。
文字列リテラルや、@type 名以外を指す型注釈の位置は対象外で null を返す。
補完
textDocument/completion は、ドット (.) を trigger character として起動し、recv. / recv.<部分名> のドット直後でレシーバのメンバ候補 (CompletionItem[]) を返す。ドット文脈でない位置 (識別子の途中・行頭など) では空配列を返す。候補のフィルタ (部分名による絞り込み) はクライアント側が行うため、サーバは部分名に依らず全候補を返す。
末尾ドット (recv.) は構文上プロパティを欠くためパースが失敗する。これを避けるため、ドット直後に仮の識別子を挿入したソースを再パースして recv.<仮> の dot 式を構成し、hover と同じレシーバ解決機構で候補を集める。レシーバを次の系統に分けて解決する:
- **標準ライブラリ (
std:*) モジュール束縛** (math := @import "std:math"のmath.): そのモジュール直下メンバ (sqrt等、hover と同じ情報源) をFunction種別で出す。std モジュールは型メソッド・universal method を持たないため、それらは混ぜない。 - ユーザ定義オブジェクト/モジュールに帰着するレシーバ (
p./ import 束縛): そのスロットをField種別で出す (確定すればname: 型を detail に付す)。 - 静的型が確定するレシーバ (
xs./s.等): その型の record フィールド・原始型メソッド (prelude method)・universal method (matches/compare/inspect) を出す。Detailに「形」、Documentationに「一行の意味」を載せる。
サブオブジェクトのメソッドや演算子メソッド糖衣 (+ 等) は対象外。レシーバが解決できない位置では空配列を返す。
整形
textDocument/formatting は、ドキュメント全体を mie fmt (format.md / format パッケージ) と同じ実体で整形し、ドキュメント全体を覆う 1 件の TextEdit (先頭 (0,0) から最終行末尾までを整形後ソースで置換) を返す。整形オプション (tabSize / insertSpaces) は受理するが、mie fmt が固定 2-space 整形のため無視する。行幅は CLI mie fmt と同じく、ファイルのあるディレクトリから祖先探索した mie.fmt.mie の width を尊重する (format.md「整形設定」。設定が無い / ファイルパスでない URI は既定幅)。次の場合は空配列を返してバッファを変更しない:
- 整形結果が元ソースと同一 (整形済み)
- ソースに構文エラーがある (整形中に走ってもバッファを壊さない)
- 未登録ドキュメント
コードアクション
textDocument/codeAction は、mie lint (mie-command.md §9) の自動修正可能な所見に対する修正アクションを返す。現状の対象は control-juxtaposition (制御構造のタプル形) のみ。次の 2 種を返す:
- quickfix (電球): 要求
rangeにcontrol-juxtapositionの診断が重なるときに出す。isPreferred: true。 - source.fixAll: 保存時修正 (
editor.codeActionsOnSave) 用。ファイル内に修正対象があればrangeに依らず出す。
いずれも修正内容は同一で、mie lint --fix (mie-command.md §9) と同じ実体 (format パッケージの制御構造並置化 + 整形) でドキュメント全体を書き換える WorkspaceEdit (全文置換 TextEdit 1 件)。行幅は 整形 と同じく mie.fmt.mie の width を尊重する。context.only が指定されたときは、その CodeActionKind に一致するアクションだけを返す (only の要素が対象 kind の接頭辞なら一致)。修正対象が無い / 構文エラー / 未登録ドキュメントでは空配列を返す。
シンボル一覧
textDocument/documentSymbol は、ファイル内のトップレベル宣言を DocumentSymbol[] の階層で返す (アウトライン / ブレッドクラム / シンボル移動の情報源)。各シンボルは range (宣言全体) と selectionRange (名前部分) を持ち、値がオブジェクトリテラルの宣言はその slot と body 内の束縛を children として入れ子にする。
対象と SymbolKind:
name := exprの束縛・slot・分解束縛 ([a, b] := …) の各名 — 値がオブジェクト (body あり) なら Function、オブジェクト (body なし) なら Struct、大文字始まりは Constant、それ以外は slot なら Field / 束縛なら Variable。@type Name := …→ Struct、@enum Name := …→ Enum (slot-list 位置・body 位置のどちらの記法も対象)。
name = expr (再代入形) は、新規 mutable local の宣言と外側束縛の再代入を静的に区別できずシンボルが重複するため対象外 (明示宣言 := / 分解束縛 / slot / @type・@enum のみをシンボルにする)。破棄子 _ も対象外。構文が壊れていても可能な範囲でパースして部分的なシンボルを返す。未登録ドキュメントは空配列。
リネーム
textDocument/rename は、カーソル位置のシンボルの全出現 (宣言 + 参照) を新しい名前へ置換する WorkspaceEdit を返す。出現の収集は references (サポート method の逆適用) を再利用するため、bare identifier だけでなく dot property (obj.x) も揃って置換される。
参照探索が現在のファイル単一のため、定義が現在のファイルにあるシンボルだけをリネーム可能とする。次はエラーを返す (VSCode はメッセージを表示):
- 未解決 / builtin (定義が処理系内部)、定義が別ファイル (
@import先) や prelude にあるシンボル — この 1 ファイルだけ書き換えると定義側と食い違うため。 - 新しい名前が mie の識別子として不正 (空 / 数字始まり / 記号を含む)、またはリテラル語 (
true/false/nil)。
textDocument/prepareRename は prepareProvider を立てて公開し、リネーム前にカーソル位置がリネーム可能かを検証して対象の識別子範囲を返す (不可ならエラー)。
インレイヒント
textDocument/inlayHint は、明示的な型注釈を持たない := 束縛に、静的推論した型を : T のインラインヒント (Kind=Type) として名前の直後に差し込む (例: n := 42 を n: Int := 42 のように見せる)。型は hover と同じ推論 (typecheck.TypeAt、import 越しも解決) で求める。
対象は name := expr 束縛・分解束縛 ([a, b] := …) の各名・値つき slot (name := default)。次は出さない:
- 明示型注釈がある束縛 (
x: Int := …)。 - RHS が
{…}(関数/データオブジェクトリテラル) の束縛 — 型が構文上明白で: {Any | Any}のような冗長なヒントになるため。リスト[…]は要素型 (: List(Int)) に価値があるので出す。 - 推論結果が
Any(情報量ゼロ) や内部の型変数。破棄子_。
要求 range (ビューポート) 内の束縛のみを計算する。未登録ドキュメントは空配列。
vscode-mie 拡張の設定 mie.inlayHints.enabled (既定 true) が OFF のとき、ヒントは返さない (空配列)。拡張は起動時に値を LSP の initializationOptions ({"inlayHints":{"enabled":<bool>}}) で渡し、設定変更時は workspace/didChangeConfiguration ({"settings":{"inlayHints":{"enabled":<bool>}}}) で通知する。サーバは値が変化したとき workspace/inlayHint/refresh をクライアントへ送り、ウィンドウのリロードなしで表示/非表示を即時反映させる。
出現ハイライト
textDocument/documentHighlight は、カーソル下シンボルの同名出現 (宣言 + 参照) を現在のファイル内で返し、エディタが淡くハイライトする。出現収集は references (サポート method の「Definition の逆適用」) をそのまま再利用するため、bare identifier も dot property も揃う。Kind は付けず一律 Text 扱い (全出現を同色でハイライト)。builtin / 未解決 / 未登録ドキュメントは空配列。
定義ジャンプ
textDocument/definition は次の順で解決する:
- ユーザ定義シンボル — 束縛・slot・inner-name・分解束縛 LHS・
obj.slot・import 越し (mod.foo/ 分解束縛名の import 先透過)。@importの文字列リテラル上では import 先ファイルの先頭。 - prelude self-host メンバ — rebind されていない prelude.mie 定義の大域名・型メソッド。処理系埋め込みの prelude.mie を指す仮想ドキュメント Location (
mie-internal:/prelude.mie) を返す。クライアントはカスタム requestmie/internalSource(params{uri}→ result{content}) で本文を取得して表示する — 手元に物理ソースが無い配布バイナリ単体でも定義先を開ける。 - 処理系実装ソース — Go / mie 実装の組込: flat 組込 (
print/if等)・型語彙 (Int/List/OneOf等の組み込み型名。型注釈・式位置とも)・原始型メソッド (xs.map等)・universal method (matches等。ドット形に加え中置形v matches Tも)・std モジュールメンバ (math.sqrt等、分解束縛名[sqrt] := @import "std:math"の参照含む)・std 不透明型メソッド (Instant/Array/OrderedMap等のt.epoch_ms等)。型メソッド・不透明型メソッドはレシーバの静的型が確定する場合のみ (universal method はレシーバ型が確定しなくても全型共通の実装位置へ解決する)。処理系ソースツリーが見つかるときだけ、その実装ソースの doc コメント位置 (Go の//|ブロック) / 定義行 (std:math self-host の math.mie) を指す file Location を返す。
処理系ソースツリーは次の順で探索する (最初に internal/prelude/prelude.mie を含むものを採用):
$MIE_REPO(mie buildの本体ソース解決 mie-command.md §4.7 と同じ変数)。設定されていれば検証のみ行い、不正でも他候補へはフォールバックしない- ビルド時に記録されたソースパス — リポジトリから go build した開発ビルドならそのリポジトリ。
-trimpathビルド (配布バイナリ) では無効 - カレントディレクトリから上位へ最大 6 階層
- 実行バイナリ隣接の
../share/mie(配布レイアウトの暫定慣例)
見つからなければ 3 のフォールバックだけが無効になり、該当シンボルは定義なし (null)。ただし分解束縛した std メンバ名 ([sqrt] := @import "std:math" の sqrt) の参照は分解束縛 LHS (ローカル定義) へフォールバックする — ソースを同梱しない環境でも他の解決は退行しない。位置索引は初回要求時に発見したツリーの internal/prelude / internal/stdmod の doc コメント (//| / #|) を走査して構築し、プロセス内でキャッシュする (焼き込みではなく実ファイルから引くため、ジャンプ先ソースの行ずれと食い違わない)。