x/text: fix nil dereference in gotext extract

Ident.Obj may be nil if the referenced declaration is in another file.

Fixes golang/go#60555

Change-Id: I730af89c7f52540b305b3da8c19448d089e9744c
Reviewed-on: https://go-review.googlesource.com/c/text/+/545055
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Sean Liao <sean@liao.dev>
Auto-Submit: Emmanuel Odeke <emmanuel@orijtech.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
diff --git a/message/pipeline/extract.go b/message/pipeline/extract.go
index 23e58f0..7d8385d 100644
--- a/message/pipeline/extract.go
+++ b/message/pipeline/extract.go
@@ -597,9 +597,13 @@
 	key := []string{}
 	if ident, ok := format.(*ast.Ident); ok {
 		key = append(key, ident.Name)
-		if v, ok := ident.Obj.Decl.(*ast.ValueSpec); ok && v.Comment != nil {
-			// TODO: get comment above ValueSpec as well
-			comment = v.Comment.Text()
+		// Ident.Obj may be nil if the referenced declaration is in another
+		// file.
+		if ident.Obj != nil {
+			if v, ok := ident.Obj.Decl.(*ast.ValueSpec); ok && v.Comment != nil {
+				// TODO: get comment above ValueSpec as well
+				comment = v.Comment.Text()
+			}
 		}
 	}
 	if c := px.getComment(call.Args[0]); c != "" {
diff --git a/message/pipeline/testdata/test60555/catalog_gen.go b/message/pipeline/testdata/test60555/catalog_gen.go
new file mode 100644
index 0000000..dce54af
--- /dev/null
+++ b/message/pipeline/testdata/test60555/catalog_gen.go
@@ -0,0 +1,38 @@
+// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
+
+package main
+
+import (
+	"golang.org/x/text/language"
+	"golang.org/x/text/message"
+	"golang.org/x/text/message/catalog"
+)
+
+type dictionary struct {
+	index []uint32
+	data  string
+}
+
+func (d *dictionary) Lookup(key string) (data string, ok bool) {
+	p, ok := messageKeyToIndex[key]
+	if !ok {
+		return "", false
+	}
+	start, end := d.index[p], d.index[p+1]
+	if start == end {
+		return "", false
+	}
+	return d.data[start:end], true
+}
+
+func init() {
+	dict := map[string]catalog.Dictionary{}
+	fallback := language.MustParse("en-US")
+	cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
+	if err != nil {
+		panic(err)
+	}
+	message.DefaultCatalog = cat
+}
+
+var messageKeyToIndex = map[string]int{}
diff --git a/message/pipeline/testdata/test60555/extracted.gotext.json.want b/message/pipeline/testdata/test60555/extracted.gotext.json.want
new file mode 100644
index 0000000..2abf7b7
--- /dev/null
+++ b/message/pipeline/testdata/test60555/extracted.gotext.json.want
@@ -0,0 +1,15 @@
+{
+    "language": "en-US",
+    "messages": [
+        {
+            "id": [
+                "testMessage",
+                "Test message"
+            ],
+            "key": "Test message",
+            "message": "Test message",
+            "translation": "",
+            "position": "testdata/test60555/main.go:17:10"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/message/pipeline/testdata/test60555/main.go b/message/pipeline/testdata/test60555/main.go
new file mode 100644
index 0000000..69b7529
--- /dev/null
+++ b/message/pipeline/testdata/test60555/main.go
@@ -0,0 +1,18 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"golang.org/x/text/language"
+	"golang.org/x/text/message"
+)
+
+// Ensures that we can catch any regressions with nil dereferences
+// from const declarations in other files within the same package.
+// See issue https://golang.org/issues/60555
+func main() {
+	p := message.NewPrinter(language.English)
+	p.Printf(testMessage)
+}
diff --git a/message/pipeline/testdata/test60555/message.go b/message/pipeline/testdata/test60555/message.go
new file mode 100644
index 0000000..ea2a98c
--- /dev/null
+++ b/message/pipeline/testdata/test60555/message.go
@@ -0,0 +1,7 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+const testMessage = "Test message"