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

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

このコミットは、Go言語の字句解析器(go/scannerパッケージ)におけるバグ修正に関するものです。具体的には、非ASCII文字で始まる識別子が正しく認識されず、誤ってドロップされてしまう問題(Issue 4000)を解決します。このバグは、以前の変更(CL 6454150)によって導入されたものです。

コミット

commit 77e98fb8f288811a650828e52e9f0eb2c01a2ed5
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Aug 23 17:03:33 2012 -0700

    go/scanner: don't drop identifiers starting with non-ASCII letter...

    Bug introduced with CL 6454150.

    Fixes #4000.

    R=r
    CC=golang-dev
    https://golang.org/cl/6474061

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

https://github.com/golang/go/commit/77e98fb8f288811a650828e52e9f0eb2c01a2ed5

元コミット内容

go/scanner: don't drop identifiers starting with non-ASCII letter...

Bug introduced with CL 6454150.

Fixes #4000.

R=r
CC=golang-dev
https://golang.org/cl/6474061

変更の背景

このコミットは、Go言語の字句解析器(スキャナー)が、非ASCII文字(例えば、アクセント記号付きの文字やキリル文字など)で始まる識別子を正しく処理できないというバグを修正するために行われました。この問題は、Go言語のIssue 4000として報告されており、以前の変更セット(CL 6454150)によって誤って導入されたものです。

Go言語はUnicodeをサポートしており、識別子に非ASCII文字を使用することが可能です。しかし、特定の変更により、スキャナーのロジックがこのUnicodeサポートを適切に扱えなくなり、非ASCII文字で始まる有効な識別子を不正なトークンとして扱ってしまうようになりました。これにより、そのような識別子を含むGoプログラムはコンパイルエラーとなるか、意図しない動作を引き起こす可能性がありました。

このコミットの目的は、スキャナーの識別子認識ロジックを修正し、Go言語の仕様に沿って非ASCII文字で始まる識別子も正しく字句解析できるようにすることです。

前提知識の解説

Go言語の字句解析器 (Scanner)

Go言語のコンパイラは、ソースコードを処理する際にいくつかの段階を踏みます。その最初の段階の一つが「字句解析(Lexical Analysis)」または「スキャン(Scanning)」です。字句解析器(スキャナーまたはレキサーとも呼ばれる)は、ソースコードの文字列を読み込み、意味のある最小単位である「トークン(Token)」のストリームに変換します。

例えば、var x = 10 というコードは、var(キーワード)、x(識別子)、=(代入演算子)、10(整数リテラル)といったトークンに分割されます。スキャナーは、各トークンの種類(token.IDENTtoken.INTなど)と、そのトークンに対応するソースコード上の文字列(リテラル値)を特定します。

識別子 (Identifier)

プログラミング言語における識別子とは、変数、関数、型、パッケージなどの名前として使用される文字列のことです。Go言語では、識別子は文字またはアンダースコア(_)で始まり、その後に任意の数の文字、数字、またはアンダースコアが続く必要があります。

Go言語の識別子の定義は、Unicode文字をサポートしています。これは、ASCII文字(a-z, A-Z)だけでなく、世界中の様々な言語の文字(例: 日本語、中国語、アラビア語、キリル文字など)を識別子として使用できることを意味します。この機能は、国際化されたコードベースや、特定の言語圏での開発において非常に有用です。

Unicodeと非ASCII文字

Unicodeは、世界中のほとんどの書記体系の文字を表現するための文字コード標準です。ASCIIはUnicodeの一部であり、基本的なラテン文字、数字、記号のみをカバーしています。非ASCII文字とは、ASCIIの範囲外にあるUnicode文字を指します。

Go言語のスキャナーは、識別子を認識する際に、単にASCII文字の範囲('a' <= ch && ch <= 'z''A' <= ch && ch <= 'Z')だけでなく、unicodeパッケージのIsLetter関数などを用いて、Unicodeの「文字」として定義されている範囲を考慮する必要があります。

CL (Change List)

Goプロジェクトでは、コードの変更は「Change List (CL)」という単位で管理されます。これは、Perforceなどのバージョン管理システムで使われる概念に由来しており、一連の関連する変更をまとめたものです。各CLには一意の番号が割り当てられ、コードレビューを経てGoのリポジトリにコミットされます。コミットメッセージに記載されているCL 6454150CL 6474061は、これらの変更リストの識別子です。

Issue (問題追跡システム)

Goプロジェクトでは、バグ報告、機能要望、議論などは「Issue」としてGitHubのIssueトラッカー(または以前はGoの独自のバグトラッカー)で管理されます。各Issueには一意の番号が割り当てられ、開発者はその番号を参照して特定のバグや機能に関連する作業を行います。コミットメッセージに記載されているFixes #4000は、このコミットがIssue 4000で報告された問題を解決することを示しています。

技術的詳細

このバグは、src/pkg/go/scanner/scanner.go 内の Scanner 構造体の scan メソッドにおける字句解析ロジックの不備に起因していました。

元々のコードでは、トークンの種類を判別する switch ch := s.ch; ステートメント内で、識別子の開始文字を判別するロジックが以下のように分かれていました。

  1. 'a' <= ch && ch <= 'z' のケース: 小文字のASCII文字で始まる識別子を処理。
  2. 'A' <= ch && ch <= 'Z' || ch == '_' のケース: 大文字のASCII文字またはアンダースコアで始まる識別子を処理。

この分離されたロジックの問題点は、isLetter(ch) のようなUnicode対応の関数を使用せず、明示的にASCII文字の範囲でしか識別子の開始をチェックしていなかったことです。そのため、非ASCII文字(例: ŝ)で始まる識別子がこれらの条件に合致せず、次の default ケースにフォールスルーしてしまっていました。

さらに、default ケース内にも問題がありました。

			if isLetter(ch) {
				// handle any letters we might have missed
				insertSemi = true
				tok = token.IDENT
				s.scanIdentifier()
			} else {
				s.error(s.file.Offset(pos), fmt.Sprintf("illegal character %#U", ch))
				insertSemi = s.insertSemi // preserve insertSemi info
				tok = token.ILLEGAL
				lit = string(ch)
			}

この default ケースは、本来は予期しない不正な文字を処理するためのものでしたが、ここでも isLetter(ch) を使って「見逃した文字」を識別子として処理しようとしていました。しかし、このロジックは s.scanIdentifier() を呼び出すだけで、その結果を lit 変数に代入していませんでした。そのため、たとえ isLetter(ch)true を返しても、識別子のリテラル値が取得されず、結果的に識別子が「ドロップ」されてしまうという問題が発生していました。

このバグは、CL 6454150という以前の変更で導入されたとされていますが、そのCLの具体的な内容は今回の情報からは特定できませんでした。しかし、おそらくその変更がUnicode識別子のサポートを拡張しようとした際に、スキャナーの既存のロジックとの整合性が取れていなかったか、あるいはリファクタリングの過程でこの不具合が紛れ込んだものと推測されます。

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

このコミットでは、主に src/pkg/go/scanner/scanner.gosrc/pkg/go/scanner/scanner_test.go の2つのファイルが変更されています。

src/pkg/go/scanner/scanner.go

  • scanAgain ラベル内の switch ch ステートメントの変更:
    • 'a' <= ch && ch <= 'z' のケースが削除されました。
    • 'A' <= ch && ch <= 'Z' || ch == '_' のケースが削除されました。
    • これらの代わりに、isLetter(ch) という単一のケースが追加され、すべての種類の文字(ASCIIおよび非ASCII)で始まる識別子を統一的に処理するようになりました。これにより、識別子の開始文字の判定ロジックが簡素化され、Unicode対応が強化されました。
  • default ケースの変更:
    • if isLetter(ch) ブロックが完全に削除されました。
    • これにより、default ケースは、isLetter(ch) で識別子として認識されるべき文字が誤ってここに到達した場合に、それを不正な文字として扱うようになりました。これは、isLetter(ch)switch ステートメントのより上位のケースで適切に処理されるようになったため、default ケースで再度識別子をチェックする必要がなくなったためです。結果として、default ケースは純粋に「不正な文字」を処理する役割に特化されました。

src/pkg/go/scanner/scanner_test.go

  • テストケースの追加:
    • ŝ (ラテン文字Sにサーカムフレックス) と ŝfoo という2つの新しい識別子テストケースが tokens 配列に追加されました。これらのテストケースには // was bug (issue 4000) というコメントが付されており、Issue 4000で報告されたバグが修正されたことを検証するためのものであることが示されています。

コアとなるコードの解説

このコミットの核心は、Go言語の識別子定義がUnicode文字を包含しているという事実を、スキャナーの字句解析ロジックに正しく反映させた点にあります。

変更前は、スキャナーは識別子の開始文字をASCIIの小文字、大文字、またはアンダースコアに限定してチェックしていました。これにより、ŝ のような非ASCII文字で始まる有効な識別子が、これらの条件に合致せず、不正な文字として扱われるか、あるいは default ケースの不完全なロジックによって正しくトークン化されないという問題がありました。

修正後のコードでは、switch ch ステートメントの最初のケースで isLetter(ch) を使用することで、Go言語の仕様で定義されている「文字」であれば、それがASCII文字であろうと非ASCII文字であろうと、すべて識別子の開始文字として正しく認識されるようになりました。isLetter 関数はGoの unicode パッケージによって提供されており、Unicodeの文字カテゴリに基づいて文字が「文字」であるかどうかを正確に判断します。

また、default ケースから if isLetter(ch) ブロックを削除したことも重要です。これにより、スキャナーのロジックがより明確になり、識別子として認識されるべき文字は必ず isLetter(ch) のケースで処理され、default ケースには本当に不正な文字のみが到達するようになりました。これにより、以前の default ケースで発生していた、識別子のリテラル値が正しく取得されないという二次的なバグも解消されました。

scanner_test.go に追加されたテストケースは、この修正が意図通りに機能していることを検証します。ŝŝfoo のような非ASCII文字で始まる識別子が token.IDENT として正しく認識されることを確認することで、Issue 4000のバグが完全に解決されたことを保証しています。

この変更により、Go言語のコンパイラは、Unicode識別子をより堅牢にサポートし、国際化されたコードベースの記述をよりスムーズに行えるようになりました。

関連リンク

  • Go CL 6474061: https://golang.org/cl/6474061
  • Go Issue 4000: (公式のIssueトラッカーへの直接リンクは見つかりませんでしたが、このコミットが修正したバグとして参照されています。)

参考にした情報源リンク

  • Go issue 4000に関するWeb検索結果: (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGlR2QkSndp8QCYAFEvN3Y7UmwNl2i8JoT6ajpJX1pKd1VnIBAzUZkOQWiamQnqw4Fpi8Sulhb4qbUoF2cLSNRxy6ldifwScEjSM-mVpHCetPK6b8Pew5VqDWKEgg8ygyjfNOVFNR6wfw=)
  • Go CL 6454150に関するWeb検索結果: (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQzjqw-9qFOdEqkL3oDK0iJvTsbAxu92He2mirXugHLix2MahFJ7nPbJqtAYB89cFoiT-fuaSdgN3qmjMuvLPc38dLX7vkP_WPKPooI4pGbu4WYJgHH3zezeMkD1PaHt4o_N_WFyB3PIVT5A==)
  • Go言語の仕様 (識別子に関する記述): (一般的なGo言語の仕様書を参照)
  • Go言語のunicodeパッケージ: (Go言語の公式ドキュメントを参照)
  • 字句解析器の概念: (コンパイラ理論に関する一般的な情報源を参照)