[インデックス 14008] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージにおけるエラーメッセージの改善と、いくつかのバグ修正を目的としています。特に、テンプレートの実行中に発生するエラーについて、より詳細で正確な位置情報(行番号、バイトオフセット)と、エラーが発生したコンテキスト(テンプレート名、評価中の式)を提供するよう変更されています。これにより、テンプレート開発者がエラーの原因を特定しやすくなります。
コミット
commit 7f4b4c0c0092e63f28ab874f9f564ee0e0e7f4cc
Author: Rob Pike <r@golang.org>
Date: Wed Oct 3 12:02:13 2012 +1000
text/template: better error messages during execution,
They now show the correct name, the byte offset on the line, and context for the failed evaluation.
Before:
template: three:7: error calling index: index out of range: 5
After:
template: top:7:20: executing "three" at <index "hi" $>: error calling index: index out of range: 5
Here top is the template that was parsed to create the set, and the error appears with the action
starting at byte 20 of line 7 of "top", inside the template called "three", evaluating the expression
<index "hi" $>.
Also fix a bug in index: it didn't work on strings. Ouch.
Also fix bug in error for index: was showing type of index not slice.
The real previous error was:
template: three:7: error calling index: can't index item of type int
The html/template package's errors can be improved by building on this;
I'll do that in a separate pass.
Extends the API for text/template/parse but only by addition of a field and method. The
old API still works.
Fixes #3188.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/6576058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7f4b4c0c0092e63f28ab874f9f564ee0e0e7f4cc
元コミット内容
このコミットは、text/template
パッケージにおけるテンプレート実行時のエラーメッセージを改善し、より詳細な情報を提供するように変更します。具体的には、エラーが発生したテンプレートの名前、行番号、行内でのバイトオフセット、そして評価に失敗した式そのものをエラーメッセージに含めるようになります。
また、index
関数が文字列に対して正しく動作しないバグと、index
関数がエラーメッセージで誤った型(インデックスの型ではなくスライスの型)を表示していたバグも修正されます。
この変更は、text/template/parse
パッケージのAPIをフィールドとメソッドの追加によって拡張しますが、既存のAPIとの互換性は維持されます。
変更の背景
Go言語のテンプレートエンジンである text/template
は、Webアプリケーションのビュー層や設定ファイルの生成など、様々な用途で利用されます。しかし、テンプレートの記述ミスやデータとの不整合によって実行時エラーが発生した場合、従来のエラーメッセージは情報が不足しており、問題の特定が困難でした。
例えば、以前のエラーメッセージは template: three:7: error calling index: index out of range: 5
のように、テンプレート名、行番号、そしてエラーの種類しか示していませんでした。これでは、どのテンプレートのどの部分で、具体的にどのような式が原因でエラーになったのかを把握するのが難しいという課題がありました。
このコミットは、このデバッグ体験を大幅に改善することを目的としています。エラーメッセージにバイトオフセットや評価中の式を含めることで、開発者はエラーの発生源をより正確に特定できるようになります。
さらに、index
関数に関する2つのバグも修正されています。特に、文字列に対するインデックス操作ができないという問題は、テンプレートの柔軟性を損なうものでした。これらのバグ修正も、テンプレートエンジンの堅牢性と使いやすさを向上させるために不可欠でした。
前提知識の解説
このコミットの理解には、以下のGo言語の概念と標準ライブラリに関する知識が役立ちます。
Go言語の text/template
パッケージ
text/template
パッケージは、Go言語に組み込まれたデータ駆動型テンプレートエンジンです。Goのデータ構造(構造体、マップ、スライスなど)をテンプレートに渡し、それらのデータに基づいてテキスト出力を生成するために使用されます。主な機能は以下の通りです。
- テンプレートの定義:
{{...}}
で囲まれたアクションを使って、データの表示、条件分岐 (if
)、繰り返し (range
)、別のテンプレートの埋め込み (template
) などを行います。 - パイプライン:
|
を使って、複数の関数呼び出しやデータ操作を連結できます。 - 関数: 組み込み関数やカスタム関数をテンプレート内で利用できます。
- パーシングと実行: テンプレート文字列を解析して内部的なツリー構造(AST: Abstract Syntax Tree)を構築し、そのツリーをデータと共に実行して最終的な出力を生成します。
html/template
パッケージ
html/template
パッケージは text/template
と同様の機能を提供しますが、HTML出力に特化しており、クロスサイトスクリプティング (XSS) 攻撃を防ぐための自動エスケープ機能が組み込まれています。このコミットの変更は text/template
に適用されますが、html/template
も text/template
を基盤としているため、将来的に同様の改善が適用される可能性が示唆されています。
抽象構文木 (AST) と parse
パッケージ
テンプレートエンジンは、入力されたテンプレート文字列を直接解釈するのではなく、まずそれを抽象構文木(AST)と呼ばれるツリー構造に変換します。ASTは、プログラムの構造を抽象的に表現したもので、各ノードがテンプレート内の要素(テキスト、アクション、コマンド、変数など)に対応します。
text/template/parse
パッケージは、このASTを構築する役割を担っています。このコミットでは、ASTの各ノードにソースコード上の位置情報(バイトオフセット)を付与する parse.Pos
型が導入され、エラー報告の精度向上に貢献しています。
reflect
パッケージ
Go言語の reflect
パッケージは、実行時に型情報を検査したり、値の操作を行ったりするための機能を提供します。テンプレートエンジンでは、テンプレートに渡されたデータの型を動的に調べ、フィールドへのアクセスやメソッドの呼び出しを行う際に reflect
パッケージが広く利用されます。
このコミットでは、index
関数のバグ修正において、reflect.String
型のサポートを追加するために reflect
パッケージが使用されています。また、エラーメッセージの改善においても、reflect.Value
を用いた値の評価が関わっています。
strconv
パッケージ
strconv
パッケージは、文字列と数値の間の変換(例: "123" を int
に変換、float64
を文字列に変換)を行うための機能を提供します。テンプレートのパーシング段階で、数値リテラルや文字列リテラルを解析する際に利用されます。
unicode
および utf8
パッケージ
unicode
パッケージはUnicode文字に関する情報(カテゴリ、プロパティなど)を提供し、utf8
パッケージはUTF-8エンコーディングされたバイト列とUnicodeコードポイント(rune)間の変換を扱います。テンプレートの字句解析(レキシング)において、入力文字列を文字単位で処理する際にこれらのパッケージが利用されます。このコミットでは、parse/lex.go
で utf8.DecodeRuneInString
が使用されており、バイトオフセットの計算に影響を与えています。
技術的詳細
このコミットの技術的な核心は、テンプレート実行時のエラー報告メカニズムを根本的に改善した点にあります。
-
parse.Pos
型の導入とASTノードへの位置情報の付与:src/pkg/text/template/parse/node.go
にtype Pos int
が新しく定義されました。これは、元の入力文字列におけるバイトオフセットを表します。Node
インターフェースにPosition() Pos
メソッドが追加され、すべてのASTノードが自身の開始バイト位置を報告できるようになりました。ListNode
,TextNode
,PipeNode
,ActionNode
など、すべての具体的なノード型にPos
フィールドが追加され、対応するコンストラクタ(例:newList
,newText
)もPos
引数を受け取るように変更されました。これにより、テンプレートのパーシング中に各ノードが生成される際に、そのノードがソースコードのどこに位置するかという情報が正確に記録されるようになりました。- 従来の
Line int
フィールドは互換性のために残されていますが、Pos
がより正確な位置情報を提供する主要な手段となります。
-
Tree.ErrorContext
メソッドによるエラーコンテキストの生成:src/pkg/text/template/parse/parse.go
にTree.ErrorContext(n Node) (location, context string)
という新しいメソッドが追加されました。- このメソッドは、エラーが発生した
Node
を受け取り、そのノードのPosition()
を利用して、元のテンプレート文字列 (t.text
) から正確な行番号と行内バイトオフセットを計算します。 strings.LastIndex(text, "\n")
を使って改行位置を特定し、バイトオフセットから行番号と列番号(バイトオフセット)を導出します。- さらに、エラーが発生したノードの文字列表現 (
n.String()
) を取得し、それをコンテキストとしてエラーメッセージに含めます。これにより、エラーがどの式で発生したのかが明確になります。
-
exec.go
におけるエラー報告の強化:src/pkg/text/template/exec.go
のstate
構造体に、エラー報告のために現在処理中のノードを保持するnode parse.Node
フィールドが追加されました。state.at(node parse.Node)
メソッドが導入され、テンプレートの実行ウォーク中に現在のノードをstate.node
に設定するようになりました。これにより、エラーが発生した瞬間の正確なノード情報が利用可能になります。state.errorf
メソッドが大幅に改修され、state.node
とstate.tmpl.ErrorContext
を利用して、以下のような詳細なエラーメッセージを生成するようになりました。template: <テンプレート名>:<行番号>:<バイトオフセット>: executing "<テンプレート名>" at <<評価中の式>>: <エラーメッセージ>
doublePercent
ヘルパー関数が追加され、fmt.Sprintf
のフォーマット文字列内で%
が誤って解釈されないようにエスケープ処理を行います。
-
index
関数のバグ修正:src/pkg/text/template/funcs.go
のindex
関数が、reflect.Array
,reflect.Slice
に加えてreflect.String
型も処理できるように修正されました。これにより、文字列に対するインデックス操作(例:{{index "hello" 0}}
で 'h' を取得)が可能になりました。- エラーメッセージ
can't index item of type %s
の中で、index.Type()
ではなくv.Type()
を使用するように変更され、インデックス対象の実際の型が報告されるようになりました。
これらの変更により、テンプレートの実行時エラーは、単なる行番号だけでなく、ファイル名、行番号、バイトオフセット、そしてエラーを引き起こした具体的なテンプレートアクションや式までを明示する、非常に診断性の高いメッセージとして出力されるようになります。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/text/template/parse/node.go
:Pos int
型の定義と、Node
インターフェースへのPosition() Pos
メソッドの追加。- すべてのASTノード構造体(
ListNode
,TextNode
,PipeNode
など)にPos
フィールドが追加され、対応するnewXxx
コンストラクタがPos
引数を受け取るように変更。
-
src/pkg/text/template/parse/parse.go
:Tree
構造体にtext string
(元のテンプレート文字列) とParseName string
(エラー報告用のテンプレート名) フィールドが追加。Tree.ErrorContext(n Node) (location, context string)
メソッドの追加。これがエラー位置とコンテキストを生成する主要なロジック。Parse
メソッドやノード生成関数(newList
,newText
,newAction
など)がPos
情報を適切に伝播・利用するように変更。
-
src/pkg/text/template/exec.go
:state
構造体のline int
がnode parse.Node
に変更。state.at(node parse.Node)
メソッドの追加。state.errorf
メソッドの大幅な変更。state.node
とstate.tmpl.ErrorContext
を利用して詳細なエラーメッセージを生成。walk
関数やevalXxx
関数群でstate.at(node)
が呼び出され、現在のノードが常に追跡されるように変更。
-
src/pkg/text/template/funcs.go
:index
関数がreflect.String
型を処理できるようにswitch v.Kind()
にreflect.String
を追加。index
関数のエラーメッセージcan't index item of type %s
で、index.Type()
をv.Type()
に変更。
-
src/pkg/text/template/exec_test.go
:- 新しいテストケース
TestExecError
が追加され、ネストされたテンプレートからのエラーが期待通りの詳細なフォーマットで出力されることを検証。
- 新しいテストケース
コアとなるコードの解説
src/pkg/text/template/parse/node.go
の Pos
と Node
インターフェース
// Pos represents a byte position in the original input text from which
// this template was parsed.
type Pos int
func (p Pos) Position() Pos {
return p
}
type Node interface {
// ...
Position() Pos // byte position of start of node in full original input string
}
// Example: TextNode
type TextNode struct {
NodeType
Pos // Embedded Pos field
Text []byte // The text; may span newlines.
}
func newText(pos Pos, text string) *TextNode {
return &TextNode{NodeType: NodeText, Pos: pos, Text: []byte(text)}
}
Pos
型は、テンプレートのソースコードにおけるバイトオフセットを厳密に追跡するための基盤となります。すべてのASTノードがこの Pos
を持つことで、エラー発生時にそのノードがソースコードのどこに位置していたかを正確に特定できるようになります。
src/pkg/text/template/parse/parse.go
の ErrorContext
func (t *Tree) ErrorContext(n Node) (location, context string) {
pos := int(n.Position())
text := t.text[:pos] // Get text up to the error position
byteNum := strings.LastIndex(text, "\n") // Find last newline
if byteNum == -1 {
byteNum = pos // On first line.
} else {
byteNum++ // After the newline.
byteNum = pos - byteNum // Calculate byte offset from start of line
}
lineNum := 1 + strings.Count(text, "\n") // Calculate line number
context = n.String() // Get string representation of the node
if len(context) > 20 {
context = fmt.Sprintf("%.20s...", context) // Truncate long contexts
}
return fmt.Sprintf("%s:%d:%d", t.ParseName, lineNum, byteNum), context
}
この ErrorContext
メソッドは、Pos
情報から人間が読みやすい行番号と列番号(バイトオフセット)を計算し、さらにエラーが発生したノードのテキスト表現を抽出して、エラーメッセージのコンテキストとして提供します。これは、詳細なエラーメッセージ生成の要となる部分です。
src/pkg/text/template/exec.go
の state.errorf
func (s *state) errorf(format string, args ...interface{}) {
name := doublePercent(s.tmpl.Name())
if s.node == nil {
format = fmt.Sprintf("template: %s: %s", name, format)
} else {
location, context := s.tmpl.ErrorContext(s.node) // Use ErrorContext
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
}
panic(fmt.Errorf(format, args...))
}
state.errorf
は、テンプレート実行中にエラーが発生した際に呼び出されます。ここで s.tmpl.ErrorContext(s.node)
を呼び出すことで、現在処理中のノードの正確な位置情報とコンテキストを取得し、それを新しい詳細なエラーメッセージフォーマットに組み込んでいます。これにより、以前の template: three:7: error calling index: index out of range: 5
が template: top:7:20: executing "three" at <index "hi" $>: error calling index: index out of range: 5
のように改善されました。
src/pkg/text/template/funcs.go
の index
関数
func index(item interface{}, indices ...interface{}) (interface{}, error) {
// ...
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String: // Added reflect.String
// ...
default:
return nil, fmt.Errorf("can't index item of type %s", v.Type()) // Changed from index.Type()
}
// ...
}
index
関数は、Goの reflect
パッケージを利用して、与えられた item
から indices
に基づいて要素を取得します。この変更により、reflect.String
が追加され、文字列も配列やスライスと同様にインデックスでアクセスできるようになりました。また、エラーメッセージの型表示も修正され、より正確な情報が提供されます。
関連リンク
- Go言語の
text/template
パッケージ公式ドキュメント: https://pkg.go.dev/text/template - Go言語の
html/template
パッケージ公式ドキュメント: https://pkg.go.dev/html/template - Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語の
strconv
パッケージ公式ドキュメント: https://pkg.go.dev/strconv - Go言語の
unicode
パッケージ公式ドキュメント: https://pkg.go.dev/unicode - Go言語の
utf8
パッケージ公式ドキュメント: https://pkg.go.dev/unicode/utf8 - Go issue #3188:
text/template: better error messages during execution
(このコミットが修正したIssue)
参考にした情報源リンク
- コミットハッシュ:
7f4b4c0c0092e63f28ab874f9f564ee0e0e7f4cc
- Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/6576058
- Go言語の公式ドキュメントおよびソースコード
- Go言語の
text/template
およびhtml/template
の設計に関する一般的な情報源 (例: Goブログ、技術記事など) - Go言語における
reflect
パッケージの利用に関する情報源