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

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

このコミットは、Go言語の標準ライブラリの一部であるgo/scannerパッケージのエラー処理APIを、よりGoらしい(idiomatic Go)スタイルにクリーンアップし、改善することを目的としています。具体的には、ErrorVector型を廃止し、既存のErrorList型に統合するとともに、スキャナーのエラーハンドラーをインターフェースからシンプルな関数型に変更しています。これにより、APIの使いやすさと整合性が向上しています。

コミット

commit d08dd8bec1e976ccd278403addac6ecfa349f2bf
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Feb 8 11:41:32 2012 -0800

    go/scanner: clean up error interface
    
    Issue 2856 asks for a rename of a few methods to a
    more idiomatic Go style. This is a very early API
    that evolved organically throughout the years.
    Together with the fact that ErrorVectors were embedded
    in other data structures (e.g. Parser), just renaming
    methods (e.g. GetError -> Error) would lead to undesired
    behavior (e.g., Parser would act like an Error). Instead,
    cleaned up API a bit more:
    
    - removed ErrorVector in favor of ErrorList (already
    present)
    - simplified Scanner.Init by making the error handler a
    function instead of requiring an ErrorHandler implementation
    - adjusted helper functions accordingly
    - updated Go 1 doc
    
    Fixes #2856.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5624047

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

https://github.com/golang/go/commit/d08dd8bec1e976ccd278403addac6ecfa349f2bf

元コミット内容

go/scanner: エラーインターフェースのクリーンアップ

Issue 2856は、いくつかのメソッドをよりGoらしいスタイルにリネームすることを求めています。これは長年にわたって有機的に進化してきた非常に初期のAPIです。ErrorVectorが他のデータ構造(例:Parser)に埋め込まれていたという事実と相まって、単にメソッド名を変更するだけでは(例:GetErrorErrorに)望ましくない動作(例:ParserErrorのように振る舞う)につながる可能性がありました。代わりに、APIをもう少しクリーンアップしました:

  • 既存のErrorListを優先してErrorVectorを削除しました。
  • エラーハンドラーをErrorHandlerインターフェースの実装を要求する代わりに、関数にすることでScanner.Initを簡素化しました。
  • ヘルパー関数をそれに応じて調整しました。
  • Go 1のドキュメントを更新しました。

Fixes #2856.

R=rsc CC=golang-dev https://golang.org/cl/5624047

変更の背景

このコミットの背景には、Go言語の初期のAPI設計と、その後の言語の成熟に伴う「Goらしい」コーディングスタイルの確立があります。

  1. Issue 2856の存在: コミットメッセージに明記されているように、GoのIssue 2856がこの変更の直接的なトリガーとなっています。このIssueは、go/scannerパッケージ内のいくつかのメソッド名がGoの慣用的な命名規則に沿っていないことを指摘し、リネームを提案していました。特に、エラー取得メソッドがGetErrorのようなプレフィックスを持つことが、Goの慣習(例えば、エラーを返す関数は通常Errorという名前のメソッドを持つ)に反すると考えられました。

  2. APIの有機的な進化: go/scannerパッケージのAPIは、Go言語の初期段階から「有機的に」進化してきたと述べられています。これは、初期の設計が必ずしも長期的な視点やGoの慣用的なスタイルを完全に考慮していなかった可能性を示唆しています。時間が経つにつれて、より良い設計パターンや慣習が明らかになり、それらに合わせて既存のAPIを調整する必要が生じました。

  3. ErrorVectorの埋め込みによる問題: コミットメッセージの核心的な問題提起は、ErrorVector型がParserのような他のデータ構造に埋め込まれていた点です。Goでは、構造体にインターフェースを埋め込むことで、そのインターフェースのメソッドを構造体自身が持つかのように振る舞わせることができます(Goの埋め込みフィールドのメカニズム)。もしErrorVectorErrorというメソッドを持っていた場合、Parser構造体も自動的にErrorメソッドを持つことになり、Parsererrorインターフェースを満たしてしまうという意図しない副作用が生じる可能性がありました。これは、Parserがエラーオブジェクトとして扱われるべきではない文脈で、誤ってエラーとして扱われるリスクをはらんでいました。この問題を回避しつつ、APIをよりクリーンにするために、単なるリネーム以上の根本的な変更が必要と判断されました。

これらの背景から、このコミットは単なる表面的なリネームに留まらず、Goの設計原則と慣用的なスタイルに沿った、より堅牢で理解しやすいエラー処理メカニズムを構築するための重要なステップであったと言えます。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とgo/scannergo/parserパッケージに関する知識が必要です。

1. Go言語のエラーハンドリング

Go言語では、エラーは通常、組み込みのerrorインターフェースを実装する値として扱われます。

type error interface {
    Error() string
}

慣例として、関数は通常、最後の戻り値としてerror型の値を返します。エラーがない場合はnilを返します。

2. go/tokenパッケージ

go/tokenパッケージは、Goソースコードの字句解析(スキャン)や構文解析(パース)において、ソースコード上の位置情報を管理するための基本的な型を提供します。

  • token.Pos: ソースコード内のバイトオフセットを表す型です。
  • token.Position: token.Posに加えて、ファイル名、行番号、列番号を含む、より人間が読みやすい位置情報を提供します。
  • token.FileSet: 複数のソースファイルをまとめて管理し、token.Posからtoken.Positionへの変換を可能にするための構造体です。

3. go/scannerパッケージ

go/scannerパッケージは、Goソースコードをトークン(識別子、キーワード、演算子など)に分割する字句解析器(スキャナー)を提供します。

  • scanner.Scanner: 字句解析を行う主要な構造体です。Initメソッドで初期化され、Scanメソッドで次のトークンを読み取ります。
  • エラーハンドリングの仕組み(変更前):
    • scanner.ErrorHandlerインターフェース: 変更前は、スキャナーがエラーを報告するためのインターフェースでした。
      type ErrorHandler interface {
          Error(pos token.Position, msg string)
      }
      
      スキャナーの利用者はこのインターフェースを実装し、Scanner.Initメソッドに渡すことで、カスタムのエラー処理ロジックを提供できました。
    • scanner.ErrorVector構造体: ErrorHandlerインターフェースを実装し、発生したエラーを内部のスライス([]*Error)に収集する具体的な型でした。これは、複数のエラーをまとめて管理するために使用されました。
      type ErrorVector struct {
          errors []*Error
      }
      func (h *ErrorVector) Error(pos token.Position, msg string) { ... }
      func (h *ErrorVector) GetError(mode int) error { ... }
      
      ErrorVectorは、GetErrorメソッドを通じて収集したエラーのリストをerror型として返す機能を持っていました。
  • scanner.ErrorList: 変更前から存在していた型で、[]*Errorのエイリアスであり、エラーのリストを表します。sort.Interfaceを実装しており、エラーを位置でソートする機能を持っていました。

4. go/parserパッケージ

go/parserパッケージは、go/scannerパッケージを利用してGoソースコードを抽象構文木(AST)に構文解析する機能を提供します。

  • parser.parser構造体: 構文解析を行う主要な構造体です。内部でscanner.Scannerを持ち、字句解析の結果を利用して構文解析を行います。
  • エラーハンドリングとの連携: parser.parserは、内部にscanner.ErrorVectorを埋め込むことで、スキャナーやパーサー自身が生成するエラーを収集していました。

5. Goの埋め込みフィールド (Embedded Fields)

Go言語では、構造体の中に型を宣言なしで埋め込むことができます。これにより、埋め込まれた型のメソッドが、その構造体のメソッドであるかのように振る舞います。

type MyError struct {
    Msg string
}
func (e MyError) Error() string { return e.Msg }

type MyStruct struct {
    MyError // MyErrorを埋め込む
    Value int
}

func main() {
    s := MyStruct{MyError: MyError{Msg: "something went wrong"}, Value: 10}
    fmt.Println(s.Error()) // MyStructがMyErrorのError()メソッドを直接呼び出せる
}

この機能はコードの再利用性を高めますが、今回のコミットの背景で述べられているように、意図しないインターフェースの実装を引き起こす可能性もあります。

これらの前提知識を理解することで、コミットがなぜ行われたのか、そしてその変更がGoのエコシステム全体にどのような影響を与えるのかを深く把握することができます。

技術的詳細

このコミットの技術的詳細は、主にgo/scannerパッケージのエラー処理メカニズムの再設計に集約されます。

1. ErrorVectorの廃止とErrorListへの統合

  • 変更前: scanner.ErrorVectorは、ErrorHandlerインターフェースを実装し、エラーを収集するための独立した構造体でした。これは、GetErrorListGetErrorといったメソッドを通じて、収集したエラーのリストを返す機能を持っていました。
  • 変更後: ErrorVector型は完全に削除されました。その機能は、既存のscanner.ErrorList型に統合されました。
    • ErrorListは元々[]*Errorのエイリアスでしたが、このコミットにより、エラーを追加するAddメソッド、リセットするResetメソッド、ソートするSortメソッド、重複を削除するRemoveMultiplesメソッド、そしてerrorインターフェースを満たすErrメソッドが追加されました。
    • これにより、ErrorList自体がエラーの収集と管理の責任を持つようになり、ErrorVectorという中間層が不要になりました。
    • ErrorListerrorインターフェースを満たすError()メソッドを持つことで、ErrorListのインスタンスを直接error型の変数に代入できるようになりました。

2. ErrorHandlerインターフェースから関数型への変更

  • 変更前: scanner.ErrorHandlerは、エラーを報告するためのインターフェースでした。
    type ErrorHandler interface {
        Error(pos token.Position, msg string)
    }
    
    Scanner.Initメソッドは、このインターフェースの実装を受け取っていました。
  • 変更後: ErrorHandlerはインターフェースではなく、シンプルな関数型(シグネチャ)になりました。
    type ErrorHandler func(pos token.Position, msg string)
    
    Scanner.Initメソッドは、この関数型の引数を受け取るように変更されました。
    • この変更により、エラーハンドラーを実装するためにわざわざ新しい型を定義する必要がなくなり、匿名関数や既存の関数を直接渡せるようになりました。これはGoの関数型プログラミングの慣習に沿ったものです。
    • 例えば、ErrorListAddメソッドを直接エラーハンドラーとして利用できるようになりました(func(pos token.Position, msg string) { myErrorList.Add(pos, msg) })。

3. parserパッケージにおけるエラー処理の変更

  • go/parserパッケージのparser構造体は、以前はscanner.ErrorVectorを埋め込んでいました。
  • このコミットにより、parser構造体はscanner.ErrorVectorの代わりにscanner.ErrorListを直接持つようになりました。
    // 変更前
    type parser struct {
        file *token.File
        scanner.ErrorVector // ErrorVectorを埋め込み
        // ...
    }
    
    // 変更後
    type parser struct {
        file    *token.File
        errors  scanner.ErrorList // ErrorListを直接持つ
        // ...
    }
    
  • これにより、parserErrorVectorのメソッド(例: Error())を継承してしまい、parser自身がerrorインターフェースを満たしてしまうという、コミットメッセージで述べられていた「望ましくない動作」が回避されました。
  • エラーの追加は、p.Error(p.file.Position(pos), msg)からp.errors.Add(p.file.Position(pos), msg)のように、明示的にErrorListAddメソッドを呼び出す形に変更されました。
  • 最終的なエラーの取得も、p.errors()からp.errors.Err()に変更され、ErrorListが持つErr()メソッドが利用されるようになりました。

4. ドキュメントの更新

  • doc/go1.htmldoc/go1.tmplが更新され、go/scannerパッケージのエラーハンドリングの変更(ErrorHandlerが関数になったこと、ErrorVectorErrorListに置き換えられたこと)が反映されました。これは、Go 1のリリースに向けたAPIの安定化と明確化の一環です。

これらの変更は、Goのエラー処理の設計原則、特に「エラーは値である」という考え方をより強く反映し、APIの整合性と使いやすさを向上させるものです。また、埋め込みフィールドの利用における潜在的な落とし穴を回避する良い例とも言えます。

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

このコミットにおける主要なコード変更は、以下のファイルに集中しています。

  1. src/pkg/go/scanner/errors.go:

    • ErrorHandlerインターフェースとErrorVector構造体の定義が完全に削除されました。
    • ErrorList型に、エラーを追加するAdd、リセットするReset、ソートするSort、重複を削除するRemoveMultiples、そしてerrorインターフェースを満たすErrメソッドが追加されました。
    • Error構造体のError()メソッドのレシーバーがポインタから値に変更されました (*ErrorからError)。
  2. src/pkg/go/scanner/scanner.go:

    • ErrorHandlerがインターフェースから関数型type ErrorHandler func(pos token.Position, msg string)に変更されました。
    • Scanner.Initメソッドのerr引数の型がErrorHandlerインターフェースから新しい関数型に変更されました。
    • s.errorメソッド内でエラーハンドラーを呼び出す部分が、s.err.Error(...)からs.err(...)に変更されました。
  3. src/pkg/go/parser/parser.go:

    • parser構造体のフィールドscanner.ErrorVectorerrors scanner.ErrorListに変更されました。
    • parser.initメソッド内でscanner.Scannerを初期化する際に、エラーハンドラーとしてp.errors.Addをラップした匿名関数が渡されるようになりました。
    • parser.errors()メソッドが削除され、代わりにParseFile内でp.errors.Err()が直接呼び出されるようになりました。
    • エラーを報告するp.errorメソッド内で、p.Error(...)の代わりにp.errors.Add(...)が呼び出されるようになりました。
    • p.ErrorCount()の代わりにp.errors.Len()が使用されるようになりました。
  4. src/pkg/go/parser/interface.go:

    • ParseFile関数内で、エラーのソートと重複削除のロジックがp.errors()の呼び出しから、p.errors.RemoveMultiples()またはp.errors.Sort()p.errors.Err()の直接呼び出しに変更されました。
  5. src/pkg/exp/types/check.go:

    • checker構造体のフィールドscanner.ErrorVectorerrors scanner.ErrorListに変更されました。
    • エラーを報告するerrorfメソッド内で、c.Error(...)の代わりにc.errors.Add(...)が呼び出されるようになりました。
    • 最終的なエラーの取得がc.GetError(...)からc.errors.Err()に変更されました。
  6. src/pkg/go/ast/resolve.go:

    • pkgBuilder構造体のフィールドscanner.ErrorVectorerrors scanner.ErrorListに変更されました。
    • エラーを報告するerrorメソッド内で、p.Error(...)の代わりにp.errors.Add(...)が呼び出されるようになりました。
    • 最終的なエラーの取得がp.GetError(...)からp.errors.Err()に変更されました。
  7. doc/go1.html および doc/go1.tmpl:

    • go/scannerパッケージのエラーハンドリングに関する説明が更新され、ErrorHandlerが関数になったこと、ErrorVectorErrorListに置き換えられたことが明記されました。

これらの変更は、go/scannerのエラー処理APIの根本的な再構築と、それを利用するgo/parserexp/typesgo/astといった上位パッケージのコードの適応を示しています。

コアとなるコードの解説

ここでは、上記の「コアとなるコードの変更箇所」で挙げた主要なファイルにおける具体的なコードの変更とその意味を詳しく解説します。

1. src/pkg/go/scanner/errors.go

このファイルは、go/scannerパッケージのエラー関連の型とメソッドを定義しています。

変更のハイライト:

  • ErrorHandlerインターフェースの削除:

    --- a/src/pkg/go/scanner/errors.go
    +++ b/src/pkg/go/scanner/errors.go
    @@ -11,14 +11,18 @@ import (
     	"sort"
     )
    
    -// An implementation of an ErrorHandler may be provided to the Scanner.
    -// If a syntax error is encountered and a handler was installed, Error
    -// is called with a position and an error message. The position points
    -// to the beginning of the offending token.
    -//
    -type ErrorHandler interface {
    -	Error(pos token.Position, msg string)
    -}
    

    ErrorHandlerインターフェースが完全に削除されました。これは、エラーハンドリングのメカニズムがインターフェースベースから関数ベースへと移行したことを意味します。

  • ErrorVector構造体の削除とErrorListへの機能統合:

    --- a/src/pkg/go/scanner/errors.go
    +++ b/src/pkg/go/scanner/errors.go
    @@ -26,24 +26,38 @@ type ErrorVector struct {
     // Within ErrorVector, an error is represented by an Error node. The
     // position Pos, if valid, points to the beginning of the offending
     // token, and the error condition is described by Msg.
    +// In an ErrorList, an error is represented by an *Error.
    +// The position Pos, if valid, points to the beginning of
    +// the offending token, and the error condition is described
    +// by Msg.
     //
     type Error struct {
     	Pos token.Position
     	Msg string
     }
    
    -func (e *Error) Error() string {
    +// Error implements the error interface.
    +func (e Error) Error() string {
     	if e.Pos.Filename != "" || e.Pos.IsValid() {
     		// don't print "<unknown position>"
     		// TODO(gri) reconsider the semantics of Position.IsValid
    @@ -57,9 +71,19 @@ func (e *Error) Error() string {
     	return e.Msg
     }
    
    -// An ErrorList is a (possibly sorted) list of Errors.
    +// ErrorList is a list of *Errors.
    +// The zero value for an ErrorList is an empty ErrorList ready to use.
    +//
     type ErrorList []*Error
    
    +// Add adds an Error with given position and error message to an ErrorList.
    +func (p *ErrorList) Add(pos token.Position, msg string) {
    +	*p = append(*p, &Error{pos, msg})
    +}
    +
    +// Reset resets an ErrorList to no errors.
    +func (p *ErrorList) Reset() { *p = (*p)[0:0] }
    +
     // ErrorList implements the sort Interface.
     func (p ErrorList) Len() int      { return len(p) }
     func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
    @@ -84,72 +108,47 @@ func (p ErrorList) Less(i, j int) bool {
     	return false
     }
    
    +// Sort sorts an ErrorList. *Error entries are sorted by position,
    +// other errors are sorted by error message, and before any *Error
    +// entry.
    +//
    +func (p ErrorList) Sort() {
    +	sort.Sort(p)
    +}
    +
    +// RemoveMultiples sorts an ErrorList and removes all but the first error per line.
    +func (p *ErrorList) RemoveMultiples() {
    +	sort.Sort(p)
    +	var last token.Position // initial last.Line is != any legal error line
    +	i := 0
    +	for _, e := range *p {
    +		if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line {
    +			last = e.Pos
    +			(*p)[i] = e
    +			i++
    +		}
    +	}
    +	(*p) = (*p)[0:i]
    +}
    +
    +// An ErrorList implements the error interface.
     func (p ErrorList) Error() string {
     	switch len(p) {
     	case 0:
    -		return "unspecified error"
    +		return "no errors"
     	case 1:
     		return p[0].Error()
     	}
     	return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1)
     }
    
    -// These constants control the construction of the ErrorList
    -// returned by GetErrors.
    -//
    -const (
    -	Raw         = iota // leave error list unchanged
    -	Sorted             // sort error list by file, line, and column number
    -	NoMultiples        // sort error list and leave only the first error per line
    -)
    -
    -// GetErrorList returns the list of errors collected by an ErrorVector.
    -// The construction of the ErrorList returned is controlled by the mode
    -// parameter. If there are no errors, the result is nil.
    -//
    -func (h *ErrorVector) GetErrorList(mode int) ErrorList {
    -	if len(h.errors) == 0 {
    -		return nil
    -	}
    -
    -	list := make(ErrorList, len(h.errors))
    -	copy(list, h.errors)
    -
    -	if mode >= Sorted {
    -		sort.Sort(list)
    -	}
    -
    -	if mode >= NoMultiples {
    -		var last token.Position // initial last.Line is != any legal error line
    -		i := 0
    -		for _, e := range list {
    -			if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line {
    -				last = e.Pos
    -				list[i] = e
    -				i++
    -			}
    -		}
    -		list = list[0:i]
    -	}
    -
    -	return list
    -}
    -
    -// GetError is like GetErrorList, but it returns an error instead
    -// so that a nil result can be assigned to an error variable and
    -// remains nil.
    -//
    -func (h *ErrorVector) GetError(mode int) error {
    -	if len(h.errors) == 0 {
    +// Err returns an error equivalent to this error list.
    +// If the list is empty, Err returns nil.
    +func (p ErrorList) Err() error {
    +	if len(p) == 0 {
     		return nil
     	}
    -
    -	return h.GetErrorList(mode)
    -}
    -
    -// ErrorVector implements the ErrorHandler interface.
    -func (h *ErrorVector) Error(pos token.Position, msg string) {
    -	h.errors = append(h.errors, &Error{pos, msg})
    +}
    

    ErrorVector構造体とその関連メソッド(Reset, ErrorCount, GetErrorList, GetError, Error)が削除されました。代わりに、ErrorList型にAdd, Reset, Sort, RemoveMultiples, Errメソッドが追加され、エラーの収集、管理、およびerrorインターフェースとしての振る舞いをErrorList自身が担うようになりました。Error構造体のError()メソッドのレシーバーが値型になった点も注目です。

2. src/pkg/go/scanner/scanner.go

このファイルは、Goソースコードをトークンに分割するスキャナーの主要なロジックを含んでいます。

変更のハイライト:

  • ErrorHandlerの関数型への変更:
    --- a/src/pkg/go/scanner/scanner.go
    +++ b/src/pkg/go/scanner/scanner.go
    @@ -30,6 +30,13 @@ import (
     	"unicode/utf8"
     )
    
    +// An ErrorHandler may be provided to Scanner.Init. If a syntax error is
    +// encountered and a handler was installed, the handler is called with a
    +// position and an error message. The position points to the beginning of
    +// the offending token.
    +//
    +type ErrorHandler func(pos token.Position, msg string)
    +
     // A Scanner holds the scanner's internal state while processing
     // a given text.  It can be allocated as part of another data
     // structure but must be initialized via Init before use.
    @@ -103,7 +110,7 @@ const (
     // line information which is already present is ignored. Init causes a
     // panic if the file size does not match the src size.
     //
    -// Calls to Scan will use the error handler err if they encounter a
    +// Calls to Scan will invoke the error handler err if they encounter a
     // syntax error and err is not nil. Also, for each error encountered,
     // the Scanner field ErrorCount is incremented by one. The mode parameter
     // determines how comments are handled.
    @@ -134,7 +141,7 @@ func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode
     
     func (s *Scanner) error(offs int, msg string) {
     	if s.err != nil {
    -		s.err.Error(s.file.Position(s.file.Pos(offs)), msg)
    +		s.err(s.file.Position(s.file.Pos(offs)), msg)
     	}
     	s.ErrorCount++
     }
    
    ErrorHandlerがインターフェースからfunc(pos token.Position, msg string)という関数型に変わりました。これにより、Scanner.Initメソッドのerr引数もこの関数型を受け取るようになり、エラー発生時の呼び出しもs.err.Error(...)からs.err(...)という直接的な関数呼び出しに簡素化されました。

3. src/pkg/go/parser/parser.go

このファイルは、GoソースコードをASTに構文解析するパーサーの主要なロジックを含んでいます。

変更のハイライト:

  • parser構造体の変更:

    --- a/src/pkg/go/parser/parser.go
    +++ b/src/pkg/go/parser/parser.go
    @@ -18,8 +18,8 @@ import (
    
     // The parser structure holds the parser's internal state.
     type parser struct {
    -	file *token.File
    -	scanner.ErrorVector
    +	file    *token.File
    +	errors  scanner.ErrorList
     	scanner scanner.Scanner
    
     	// Tracing/debugging
    

    parser構造体からscanner.ErrorVectorの埋め込みが削除され、代わりにerrors scanner.ErrorListというフィールドが明示的に追加されました。これにより、parserErrorVectorのメソッドを意図せず継承する問題が解消されました。

  • parser.initメソッドでのスキャナー初期化:

    --- a/src/pkg/go/parser/parser.go
    +++ b/src/pkg/go/parser/parser.go
    @@ -58,7 +58,8 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod
     	if mode&ParseComments != 0 {
     		m = scanner.ScanComments
     	}
    -	p.scanner.Init(p.file, src, p, m)
    +	eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) }
    +	p.scanner.Init(p.file, src, eh, m)
    
     	p.mode = mode
     	p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
    

    Scanner.Initの呼び出しにおいて、以前はpparser自身がErrorHandlerインターフェースを満たしていた)を渡していましたが、変更後はp.errors.Addをラップした匿名関数ehを渡すようになりました。これは、ErrorListAddメソッドが新しいErrorHandler関数型のシグネチャに合致するため、直接エラーをp.errorsに追加できるようになったことを示しています。

  • エラー報告メソッドの変更:

    --- a/src/pkg/go/parser/parser.go
    +++ b/src/pkg/go/parser/parser.go
    @@ -334,7 +327,7 @@ func (p *parser) next() {
     }
    
     func (p *parser) error(pos token.Pos, msg string) {
    -	p.Error(p.file.Position(pos), msg)
    +	p.errors.Add(p.file.Position(pos), msg)
     }
    

    parser.errorメソッド内で、エラーを報告する際にp.Error(...)(埋め込まれたErrorVectorのメソッド)を呼び出す代わりに、明示的にp.errors.Add(...)を呼び出すようになりました。

  • エラーカウントの取得:

    --- a/src/pkg/go/parser/parser.go
    +++ b/src/pkg/go/parser/parser.go
    @@ -2123,7 +2116,7 @@ func (p *parser) parseFile() *ast.File {
     	// Don't bother parsing the rest if we had errors already.
     	// Likely not a Go source file at all.
    
    -	if p.ErrorCount() == 0 && p.mode&PackageClauseOnly == 0 {
    +	if p.errors.Len() == 0 && p.mode&PackageClauseOnly == 0 {
     		// import decls
     		for p.tok == token.IMPORT {
     			decls = append(decls, p.parseGenDecl(token.IMPORT, parseImportSpec))\
    

    エラーの数をチェックする際に、p.ErrorCount()ErrorVectorのメソッド)の代わりに、p.errors.Len()ErrorListのメソッド)が使用されるようになりました。

4. src/pkg/go/parser/interface.go

このファイルは、go/parserパッケージの公開インターフェースを定義しています。

変更のハイライト:

  • ParseFile関数のエラー処理ロジック:
    --- a/src/pkg/go/parser/interface.go
    +++ b/src/pkg/go/parser/interface.go
    @@ -80,13 +80,25 @@ const (
     // are returned via a scanner.ErrorList which is sorted by file position.
     //
     func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (*ast.File, error) {
    +	// get source
     	text, err := readSource(filename, src)
     	if err != nil {
     		return nil, err
     	}
    +
    +	// parse source
     	var p parser
     	p.init(fset, filename, text, mode)
    -	return p.parseFile(), p.errors()
    +	f := p.parseFile()
    +
    +	// sort errors
    +	if p.mode&SpuriousErrors == 0 {
    +		p.errors.RemoveMultiples()
    +	} else {
    +		p.errors.Sort()
    +	}
    +
    +	return f, p.errors.Err()
     }
    
    ParseFile関数内で、最終的なエラーを返す部分がp.errors()からp.errors.Err()に変更されました。また、エラーのソートや重複削除のロジックがp.errors()メソッドの内部ではなく、ParseFile関数内で明示的にp.errors.RemoveMultiples()p.errors.Sort()を呼び出す形になりました。これにより、エラー処理のフローがより明確になりました。

これらの変更は、Goのエラー処理の慣用的なパターンに沿って、APIをよりシンプルで堅牢なものにするための重要なステップでした。特に、ErrorVectorの埋め込みによる意図しないインターフェースの実装という問題を解決し、ErrorListをエラー収集の中心的な型として確立した点が大きな改善です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go/scanner, go/parser, go/tokenパッケージ)
  • Go言語のエラーハンドリングに関する一般的な慣習とベストプラクティス
  • Go言語の埋め込みフィールドに関する情報

(注:この解説は、提供されたコミット情報とGo言語に関する一般的な知識に基づいて生成されています。Web検索は、Go言語の特定の概念や慣習に関する詳細な背景情報を補完するために内部的に活用されました。)