Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16304] ファイルの概要

このコミットは、Go言語のEmacsモード(go-mode.el)における、レシーバ名が省略されたメソッドのサポートを追加するものです。具体的には、func (Foo) Bar... のような形式のメソッドに対するフォント表示(fontification)、ナビゲーション、およびインデントの問題を修正します。

コミット

commit 72b14cbb75b3b0d8899d24174a94ece100d073b7
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date:   Tue May 14 20:23:35 2013 -0700

    misc/emacs: Add support for methods with unnamed receiver
    
    This fixes fontification, navigation and indentation for methods
    of the form `func (Foo) Bar...`
    
    R=adonovan
    CC=gobot, golang-dev
    https://golang.org/cl/8951043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/72b14cbb75b3b0d8899d24174a94ece100d073b7

元コミット内容

misc/emacs: Add support for methods with unnamed receiver

このコミットは、func (Foo) Bar... の形式のメソッドに対するフォント表示、ナビゲーション、およびインデントを修正します。

変更の背景

Go言語では、メソッドを定義する際にレシーバを宣言します。通常、レシーバには変数名と型が与えられます(例: func (r MyReceiverType) MyMethod() {})。しかし、Go言語の仕様では、レシーバの変数名を省略して型のみを指定することも可能です(例: func (MyReceiverType) MyMethod() {})。これは、メソッド内でレシーバの値を参照する必要がない場合に、コードの簡潔さを保つために使用されます。

EmacsのGoモード(go-mode.el)は、Go言語のソースコードをEmacsエディタで適切に表示・操作するための主要なツールです。これには、構文ハイライト(fontification)、コードの折りたたみ、インデント、定義へのジャンプ(ナビゲーション)などの機能が含まれます。

このコミットが作成される以前は、go-mode.el がレシーバ名が省略されたメソッドの構文を正しく認識していませんでした。その結果、これらのメソッドがEmacs上で適切にハイライトされず、インデントが崩れたり、関連するナビゲーション機能が動作しないといった問題が発生していました。開発者は、このようなGo言語の正規の構文がEmacs上で正しく扱われるようにするために、go-mode.el の正規表現とフォント表示ルールを更新する必要がありました。

前提知識の解説

Go言語のメソッドとレシーバ

Go言語におけるメソッドは、特定の型に関連付けられた関数です。メソッドは、関数名の前にレシーバ引数を指定することで定義されます。レシーバは、メソッドが呼び出されるインスタンス(値またはポインタ)を表します。

例:

type MyType struct {
    value int
}

// 通常のレシーバ(変数名あり)
func (m MyType) GetValue() int {
    return m.value
}

// レシーバ名が省略されたメソッド
func (MyType) StaticMethod() {
    // このメソッド内では MyType のインスタンスを参照する必要がない
    fmt.Println("This is a static-like method.")
}

後者の形式は、レシーバの具体的な値を使用しないが、その型に属するメソッドとして定義したい場合に便利です。

Emacsのfont-lock-modeと正規表現

Emacsのfont-lock-modeは、プログラミング言語の構文をハイライトするための主要なメカニズムです。これは、バッファの内容を正規表現パターンと照合し、一致したテキストに特定のフォントフェイス(色、スタイルなど)を適用することで機能します。

go-mode.elのようなメジャーモードは、Go言語のキーワード、コメント、文字列、関数、メソッドなどを識別するための多数の正規表現を定義しています。これらの正規表現は、font-lock-modeがコードを正しく解析し、適切なハイライトを適用するために不可欠です。

  • defconst: Emacs Lispで定数を定義するために使用されます。
  • regexp: 正規表現を定義します。Emacs Lispの正規表現は、Perl互換正規表現(PCRE)に似ていますが、一部の構文が異なります。特に、バックスラッシュのエスケープルールに注意が必要です。
  • font-lock-type-face: Emacsのフォントロックモードで、型名に適用されるフォントフェイス(通常は特定の強調色)を指定します。
  • font-lock-keywords: font-lock-modeが使用する正規表現と、それらに一致したテキストに適用するフォントフェイスのリストを定義します。

Emacsのナビゲーションとインデント

Emacsのプログラミングモードは、構文解析に基づいてコードのナビゲーション(例: 関数定義へのジャンプ)や自動インデントを提供します。これらの機能は、コードの構造を正確に理解するために、構文ハイライトと同様に正規表現に依存しています。構文解析が不正確だと、これらの機能も正しく動作しません。

技術的詳細

このコミットは、go-mode.el内の2つの主要な変更によって、レシーバ名が省略されたGoメソッドのサポートを実現しています。

  1. go-func-meth-regexp の修正:

    • go-func-meth-regexp は、Go言語のメソッド定義を識別するための正規表現です。
    • 変更前は、レシーバの変数名が必須であると仮定していました。
    • 変更後、レシーバの変数名(go-identifier-regexp)がオプション(\\s *\\\\(?)として扱われるように正規表現が修正されました。これにより、func (Type) Method() のような形式もこの正規表現に一致するようになります。
    • 具体的には、"\\\\(\\\\s *" の後に go-identifier-regexpgo-type-regexp の間に \\\\s +\\\\)? が追加され、レシーバの識別子がオプションであることを示しています。
  2. font-lock-keywords への新しいルールの追加:

    • font-lock-keywords は、Emacsが構文ハイライトを行う際に使用する正規表現とフォントフェイスのペアのリストです。
    • 新しいルール (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-type-name-regexp ")") 1 font-lock-type-face) が追加されました。
    • このルールは、func (TypeName) の形式に一致し、TypeName の部分に font-lock-type-face を適用します。これは、レシーバ名がないメソッドの型名を正しくハイライトするために必要です。
    • go--regexp-enclose-in-symbol は、シンボル(この場合は func)を正規表現として囲むヘルパー関数です。
    • [[:space:]]+ は1つ以上の空白文字に一致します。
    • go-type-name-regexp はGoの型名に一致する正規表現です。

これらの変更により、EmacsのGoモードは、レシーバ名が省略されたメソッドの構文を正確に解析し、適切なフォント表示、ナビゲーション、およびインデントを提供できるようになりました。

コアとなるコードの変更箇所

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -77,7 +77,12 @@
 (defconst go-label-regexp go-identifier-regexp)\n (defconst go-type-regexp "[[:word:][:multibyte:]*]+")\n (defconst go-func-regexp (concat (go--regexp-enclose-in-symbol "func") "\\\\s *\\\\(" go-identifier-regexp "\\\\)"))\n-(defconst go-func-meth-regexp (concat (go--regexp-enclose-in-symbol "func") "\\\\s *\\\\(?:(\\\\s *" go-identifier-regexp "\\\\s +" go-type-regexp "\\\\s *)\\\\s *\\\\)?\\\\(" go-identifier-regexp "\\\\)(\\)"))\n+(defconst go-func-meth-regexp (concat\n+                               (go--regexp-enclose-in-symbol "func") "\\\\s *\\\\(?:(\\\\s *"\n+                               "\\\\(" go-identifier-regexp "\\\\s +\\\\)?\" go-type-regexp\n+                               "\\\\s *)\\\\s *\\\\)?\\\\("\n+                               go-identifier-regexp\n+                               "\\\\)(\\)"))\n (defconst go-builtins\n   '("append" "cap"   "close"   "complex" "copy"\n     "delete" "imag"  "len"     "make"    "new"\n@@ -159,6 +164,7 @@
      ;; TODO do we actually need this one or isn't it just a function call?\n      (,(concat "\\\\.\\\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type conversion\n      (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-identifier-regexp "[[:space:]]+" go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver\n+     (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver without variable name\n      ;; Like the original go-mode this also marks compound literal\n      ;; fields. There, it was marked as to fix, but I grew quite\n      ;; accustomed to it, so it'll stay for now.\n```

## コアとなるコードの解説

### `go-func-meth-regexp` の変更

元の定義:
```elisp
(defconst go-func-meth-regexp (concat (go--regexp-enclose-in-symbol "func") "\\s *\\(?:(\\s *" go-identifier-regexp "\\s +" go-type-regexp "\\s *)\\s *\\)?\\(" go-identifier-regexp "\\)(\\)"))

変更後の定義:

(defconst go-func-meth-regexp (concat
                               (go--regexp-enclose-in-symbol "func") "\\s *\\(?:(\\s *"
                               "\\(" go-identifier-regexp "\\s +\\)?\" go-type-regexp
                               "\\s *)\\s *\\)?\\("
                               go-identifier-regexp
                               "\\)(\\)"))

この変更の核心は、レシーバの定義部分 (\\s *" go-identifier-regexp "\\s +" go-type-regexp "\\s *) において、go-identifier-regexp の部分を \\(\\)? で囲むことで、レシーバの変数名がオプションであることを示している点です。

  • \\(\\) はグループ化のための正規表現構文です。
  • \\? は直前の要素が0回または1回出現することを示します。

これにより、func (receiverName Type) Method()func (Type) Method() の両方の形式に一致するようになります。

font-lock-keywords への追加

+     (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver without variable name

この行は、font-lock-keywords リストに新しいフォント表示ルールを追加しています。

  • concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-type-name-regexp ")": これは、func (TypeName) のパターンに一致する正規表現を構築します。
    • func: キーワード "func" に一致。
    • [[:space:]]+: 1つ以上の空白文字に一致。
    • (): リテラルの括弧に一致。
    • go-type-name-regexp: Goの型名に一致する正規表現。
  • 1: 正規表現の最初のキャプチャグループ(この場合は go-type-name-regexp に一致する部分)にフォントフェイスを適用することを示します。
  • font-lock-type-face: 適用されるフォントフェイス。これにより、レシーバの型名が適切にハイライトされます。

この追加により、レシーバ名が省略されたメソッドの型が、他の型と同様にEmacs上で正しくハイライトされるようになります。

関連リンク

参考にした情報源リンク