gopls/internal/lsp/mod: fix nil panic in go.mod hover

The logic now handles a missing module directive.

Fixes golang/go#57625

Change-Id: I8315c42441178bbda6770b48e5511ffbb4d53146
Reviewed-on: https://go-review.googlesource.com/c/tools/+/460858
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/lsp/mod/hover.go b/gopls/internal/lsp/mod/hover.go
index 34173e8..ef7c235 100644
--- a/gopls/internal/lsp/mod/hover.go
+++ b/gopls/internal/lsp/mod/hover.go
@@ -125,11 +125,15 @@
 }
 
 func hoverOnModuleStatement(ctx context.Context, pm *source.ParsedModule, offset int, snapshot source.Snapshot, fh source.FileHandle) (*protocol.Hover, bool) {
-	if offset < pm.File.Module.Syntax.Start.Byte || offset > pm.File.Module.Syntax.End.Byte {
-		return nil, false
+	module := pm.File.Module
+	if module == nil {
+		return nil, false // no module stmt
+	}
+	if offset < module.Syntax.Start.Byte || offset > module.Syntax.End.Byte {
+		return nil, false // cursor not in module stmt
 	}
 
-	rng, err := pm.Mapper.OffsetRange(pm.File.Module.Syntax.Start.Byte, pm.File.Module.Syntax.End.Byte)
+	rng, err := pm.Mapper.OffsetRange(module.Syntax.Start.Byte, module.Syntax.End.Byte)
 	if err != nil {
 		return nil, false
 	}
diff --git a/gopls/internal/regtest/misc/hover_test.go b/gopls/internal/regtest/misc/hover_test.go
index d3db257..dafcc62 100644
--- a/gopls/internal/regtest/misc/hover_test.go
+++ b/gopls/internal/regtest/misc/hover_test.go
@@ -253,3 +253,15 @@
 		})
 	})
 }
+
+// This is a regression test for Go issue #57625.
+func TestHoverModMissingModuleStmt(t *testing.T) {
+	const source = `
+-- go.mod --
+go 1.16
+`
+	Run(t, source, func(t *testing.T, env *Env) {
+		env.OpenFile("go.mod")
+		env.Hover("go.mod", env.RegexpSearch("go.mod", "go")) // no panic
+	})
+}