29
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

tenntennAdvent Calendar 2022

Day 4

Goの//lineコメントを君は知っているか?

Last updated at Posted at 2022-12-04

コメントディレクティブ

どうもナレッジワークtenntennです。

Goには、//go:noescape//go:linknameなどのコメントでコンパイラへの指示を記述するコメントディレクティブがあります。一応、ドキュメントには記載がありますが、多くのGoエンジニアはあまり機会はないでしょう。

ドキュメントを読むと//lineで始まるコメントは特別な意味を持つことが分かります。ここではこのlineディレクティブについて解説します。

lineディレクティブ

ドキュメントには、以下のように記述した場合に、lineディレクティブとして認識されるようです。行コメントだけはなく、ブロックコメントも対象となります。///*の後ろにスペースを含んでは行けなかったり、:が必ず含まれていなければならなかったりと、細かなルールがあります。

//line :line
//line :line:col
//line filename:line
//line filename:line:col
/*line :line*/
/*line :line:col*/
/*line filename:line*/
/*line filename:line:col*/

lineディレクティブには、ファイル名、行、列を書けます。//lineで始まる行コメントの場合は、次の行の先頭の文字が、そのファイル名、行、列であるようにコンパイラに指示します。

ファイル名を省略すると、直前のlineディレクティブによる指示と同じ、はじめてのlineディレクティブの場合は元のファイル名になります。

列を省略すると、次に列が判明する(他のlineディレクティブで)まで、その範囲は列が不明となり、コンパイラはエラーなどの報告で列情報を使用しません。

ブロックコメントで書いた場合は、*/直後の文字から対象となります。それでは、実際に書いてみましょう。次のように、2行目にpackage句も書かずにいきなりErrorと書いてみます。

//line hoge.go:100
Error

1行目にlineディレクティブがあるため、本来なら2行目でコンパイラエラーになるはずですが、コンパイルしようとすると次のようなエラーがでます。

$ go build a.go
hoge.go:100: expected 'package', found Error

hoge.goの100行目でエラーが発生していることになっています。ファイル名だけではなく、行数も変わっています。また、列は表示されていません。

それでは、次のコードはどうなるでしょうか。1行目にエラーがあります。

/*line fuga.go:200:10*/Error

ビルドしてみましょう。次のようなエラーが表示されます。

$ go run b.go
fuga.go:200:10: expected 'package', found Error

今度は列も表示されました。面白いですね。

何に使われているのか?

それではこの機能は何のためにあるのでしょうか?
実際に使われている箇所はあるのでしょうか?

実は、go test -coverを実行した際に内部で実行されるgo tool coverというカバレッジを計測するためのコードを対象のGoファイルに差し込むためのツールで使われています。

たとえば、次のようなコードa.goがあった場合を考えます。

package a

func f() {
	if true {
		println("A")
	}
	println("B")
}

go tool cover -mode set a.goを実行してみます。そうすると、次のようなソースコードが生成されます。

//line a.go:1
package a

func f() {GoCover.Count[0] = 1;
	if true {GoCover.Count[2] = 1;
		println("A")
	}
	GoCover.Count[1] = 1;println("B")
}

var GoCover = struct {
	Count     [3]uint32
	Pos       [3 * 3]uint32
	NumStmt   [3]uint16
} {
	Pos: [3 * 3]uint32{
		3, 4, 0xa000a, // [0]
		7, 7, 0xe0002, // [1]
		4, 6, 0x3000a, // [2]
	},
	NumStmt: [3]uint16{
		1, // 0
		1, // 1
		1, // 2
	},
}

1行目にlineディレクティブが記述されています。go test -coverを実行すると、go tool coverが生成したコードが元のコードの代わりにコンパイルに使用されます。そのため、コンパイルエラーがあった場合、元のファイルと違う名前でエラーメッセージが表示されてしまうため、lineディレクティブでファイル名を変更することで違和感が無いようにしています。

生成されたコードをよく見ると、ファイルの末尾以外では改行を増やさないようにコードを追加しているのが分かります。コンパイラエラーなどで行数を表示する際に変わってしまわないように工夫がされています。

go tool coverと同じように、元のソースコードと差し替えてコード生成したソースコードをコンパイルに用いるようなツールを作っている場合は、lineディレクティブは便利です。

go tool coverについては、Gopher塾 #1でも扱っています。今後のアドベントカレンダーでgo tool coverの挙動を追う方法についても書く予定ですので、楽しみにしててください。

29
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?