internal/lsp/source: add an experimental new cache key for packages

We've recently noted that hashing the packages.Config into the cache key
for the type checked package is probably unnecessary, given that all the
other critical inputs into the typechecker are already included
(packageID, parsed files, and deps). Furthermore, when using a gopls
daemon this causes unnecessary cache misses, because the packages.Config
includes things like working directory that no longer matter once other
inputs to type checking have been computed.

Add an experiment flag that removes the packages.Config from the cache
key. An experiment is used to stage this change as comprehensively
testing the cache is ~impossible.

Change-Id: I7ba73daaa71a80ec996decaa9817ec515b5eeb6e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/260737
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Robert Findley <rfindley@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index 2c00fed..255b5dc 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -273,6 +273,17 @@
 
 
 Default: `0`.
+### **experimentalPackageCacheKey** *bool*
+experimentalPackageCacheKey controls whether to use a coarser cache key
+for package type information to increase cache hits. This setting removes
+the user's environment, build flags, and working directory from the cache
+key, which should be a safe change as all relevant inputs into the type
+checking pass are already hashed into the key. This is temporarily guarded
+by an experiment because caching behavior is subtle and difficult to
+comprehensively test.
+
+
+Default: `false`.
 <!-- END Experimental: DO NOT MANUALLY EDIT THIS SECTION -->
 
 ## Debugging
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 25f3147..688d52b 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -156,7 +156,8 @@
 		deps[depHandle.m.pkgPath] = depHandle
 		depKeys = append(depKeys, depHandle.key)
 	}
-	ph.key = checkPackageKey(ctx, ph.m.id, compiledGoFiles, m.config, depKeys, mode)
+	experimentalKey := s.View().Options().ExperimentalPackageCacheKey
+	ph.key = checkPackageKey(ctx, ph.m.id, compiledGoFiles, m.config, depKeys, mode, experimentalKey)
 	return ph, deps, nil
 }
 
@@ -168,10 +169,16 @@
 	}
 }
 
-func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode) packageHandleKey {
+func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode, experimentalKey bool) packageHandleKey {
 	b := bytes.NewBuffer(nil)
 	b.WriteString(string(id))
-	b.WriteString(hashConfig(cfg))
+	if !experimentalKey {
+		// cfg was used to produce the other hashed inputs (package ID, parsed Go
+		// files, and deps). It should not otherwise affect the inputs to the type
+		// checker, so this experiment omits it. This should increase cache hits on
+		// the daemon as cfg contains the environment and working directory.
+		b.WriteString(hashConfig(cfg))
+	}
 	b.WriteByte(byte(mode))
 	for _, dep := range deps {
 		b.WriteString(string(dep))
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index 77a4c27..4b66cef 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -2,4 +2,4 @@
 
 package source
 
-const GeneratedAPIJSON = "{\"Options\":{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"100000000\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n  \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n  \\\"unusedparams\\\": true  // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"codelens overrides the enabled/disabled state of code lenses. See the \\\"Code Lenses\\\"\\nsection of settings.md for the list of supported lenses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"codelens\\\": {\\n    \\\"generate\\\": false,  // Don't show the `go generate` lens.\\n    \\\"gc_details\\\": true  // Show a code lens toggling the display of gc's choices.\\n  }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n    str string\\n}\\n\\nfunc main() {\\n    x := wrapString{\\\"hello world\\\"}\\n    fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle controls how symbols are qualified in symbol responses. It\\naccepts the following values:\\n * \\\"full\\\": symbols are fully qualified, i.e. \\\"path/to/pkg.Foo.Field\\\"\\n * \\\"package\\\": symbols are package qualified, i.e. \\\"pkg.Foo.Field\\\"\\n * \\\"dynamic\\\": symbols are qualified using whichever qualifier results in\\n    the highest scoring match for the given symbol query. Here a\\n    \\\"qualifier\\\" is any \\\"/\\\" or \\\".\\\" delimited suffix of the fully qualified\\n    symbol. i.e. \\\"to/pkg.Foo.Field\\\" or just \\\"Foo.Field\\\".\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"symbolStyle\\\": \\\"dynamic\\\",\\n...\\n}\\n```\\n\",\"EnumValues\":[\"\\\"Dynamic\\\"\",\"\\\"Full\\\"\",\"\\\"Package\\\"\"],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[\"\\\"Both\\\"\",\"\\\"Definition\\\"\",\"\\\"Link\\\"\"],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"semanticTokens\",\"Type\":\"bool\",\"Doc\":\"semanticTokens controls whether the LSP server will send\\nsemantic tokens to the client.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"literalCompletions\",\"Type\":\"bool\",\"Doc\":\"literalCompletions controls whether literal candidates such as\\n\\\"\\u0026someStruct{}\\\" are offered. Tests disable this flag to simplify\\ntheir expected values.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalDiagnosticsDelay\",\"Type\":\"time.Duration\",\"Doc\":\"experimentalDiagnosticsDelay controls the amount of time that gopls waits\\nafter the most recent file modification before computing deep diagnostics.\\nSimple diagnostics (parsing and type-checking) are always run immediately\\non recently modified packages.\\n\\nThis option must be set to a valid duration string, for example `\\\"250ms\\\"`.\\n\",\"EnumValues\":null,\"Default\":\"0\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"[]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[\"\\\"FullDocumentation\\\"\",\"\\\"NoDocumentation\\\"\",\"\\\"SingleLine\\\"\",\"\\\"Structured\\\"\",\"\\\"SynopsisDocumentation\\\"\"],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]},\"Commands\":[{\"Command\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Command\":\"fill_struct\",\"Title\":\"fill_struct\",\"Doc\":\"fill_struct is a gopls command to fill a struct with default\\nvalues.\\n\"},{\"Command\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Command\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Command\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Command\":\"undeclared_name\",\"Title\":\"undeclared_name\",\"Doc\":\"undeclared_name adds a variable declaration for an undeclared\\nname.\\n\"},{\"Command\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Command\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Command\":\"extract_variable\",\"Title\":\"Extract to variable\",\"Doc\":\"extract_variable extracts an expression to a variable.\\n\"},{\"Command\":\"extract_function\",\"Title\":\"Extract to function\",\"Doc\":\"extract_function extracts statements to a function.\\n\"},{\"Command\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"},{\"Command\":\"generate_gopls_mod\",\"Title\":\"Generate gopls.mod\",\"Doc\":\"generate_gopls_mod (re)generates the gopls.mod file.\\n\"}],\"Lenses\":[{\"Lens\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Lens\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Lens\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Lens\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Lens\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Lens\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Lens\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"}]}"
+const GeneratedAPIJSON = "{\"Options\":{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"100000000\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n  \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n  \\\"unusedparams\\\": true  // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"codelens overrides the enabled/disabled state of code lenses. See the \\\"Code Lenses\\\"\\nsection of settings.md for the list of supported lenses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"codelens\\\": {\\n    \\\"generate\\\": false,  // Don't show the `go generate` lens.\\n    \\\"gc_details\\\": true  // Show a code lens toggling the display of gc's choices.\\n  }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n    str string\\n}\\n\\nfunc main() {\\n    x := wrapString{\\\"hello world\\\"}\\n    fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle controls how symbols are qualified in symbol responses. It\\naccepts the following values:\\n * \\\"full\\\": symbols are fully qualified, i.e. \\\"path/to/pkg.Foo.Field\\\"\\n * \\\"package\\\": symbols are package qualified, i.e. \\\"pkg.Foo.Field\\\"\\n * \\\"dynamic\\\": symbols are qualified using whichever qualifier results in\\n    the highest scoring match for the given symbol query. Here a\\n    \\\"qualifier\\\" is any \\\"/\\\" or \\\".\\\" delimited suffix of the fully qualified\\n    symbol. i.e. \\\"to/pkg.Foo.Field\\\" or just \\\"Foo.Field\\\".\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"symbolStyle\\\": \\\"dynamic\\\",\\n...\\n}\\n```\\n\",\"EnumValues\":[\"\\\"Dynamic\\\"\",\"\\\"Full\\\"\",\"\\\"Package\\\"\"],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[\"\\\"Both\\\"\",\"\\\"Definition\\\"\",\"\\\"Link\\\"\"],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"semanticTokens\",\"Type\":\"bool\",\"Doc\":\"semanticTokens controls whether the LSP server will send\\nsemantic tokens to the client.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"literalCompletions\",\"Type\":\"bool\",\"Doc\":\"literalCompletions controls whether literal candidates such as\\n\\\"\\u0026someStruct{}\\\" are offered. Tests disable this flag to simplify\\ntheir expected values.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalDiagnosticsDelay\",\"Type\":\"time.Duration\",\"Doc\":\"experimentalDiagnosticsDelay controls the amount of time that gopls waits\\nafter the most recent file modification before computing deep diagnostics.\\nSimple diagnostics (parsing and type-checking) are always run immediately\\non recently modified packages.\\n\\nThis option must be set to a valid duration string, for example `\\\"250ms\\\"`.\\n\",\"EnumValues\":null,\"Default\":\"0\"},{\"Name\":\"experimentalPackageCacheKey\",\"Type\":\"bool\",\"Doc\":\"experimentalPackageCacheKey controls whether to use a coarser cache key\\nfor package type information to increase cache hits. This setting removes\\nthe user's environment, build flags, and working directory from the cache\\nkey, which should be a safe change as all relevant inputs into the type\\nchecking pass are already hashed into the key. This is temporarily guarded\\nby an experiment because caching behavior is subtle and difficult to\\ncomprehensively test.\\n\",\"EnumValues\":null,\"Default\":\"false\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"[]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[\"\\\"FullDocumentation\\\"\",\"\\\"NoDocumentation\\\"\",\"\\\"SingleLine\\\"\",\"\\\"Structured\\\"\",\"\\\"SynopsisDocumentation\\\"\"],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]},\"Commands\":[{\"Command\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Command\":\"fill_struct\",\"Title\":\"fill_struct\",\"Doc\":\"fill_struct is a gopls command to fill a struct with default\\nvalues.\\n\"},{\"Command\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Command\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Command\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Command\":\"undeclared_name\",\"Title\":\"undeclared_name\",\"Doc\":\"undeclared_name adds a variable declaration for an undeclared\\nname.\\n\"},{\"Command\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Command\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Command\":\"extract_variable\",\"Title\":\"Extract to variable\",\"Doc\":\"extract_variable extracts an expression to a variable.\\n\"},{\"Command\":\"extract_function\",\"Title\":\"Extract to function\",\"Doc\":\"extract_function extracts statements to a function.\\n\"},{\"Command\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"},{\"Command\":\"generate_gopls_mod\",\"Title\":\"Generate gopls.mod\",\"Doc\":\"generate_gopls_mod (re)generates the gopls.mod file.\\n\"}],\"Lenses\":[{\"Lens\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Lens\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Lens\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Lens\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Lens\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Lens\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Lens\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"}]}"
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 90ca263..b264bf8 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -354,6 +354,15 @@
 	//
 	// This option must be set to a valid duration string, for example `"250ms"`.
 	ExperimentalDiagnosticsDelay time.Duration
+
+	// ExperimentalPackageCacheKey controls whether to use a coarser cache key
+	// for package type information to increase cache hits. This setting removes
+	// the user's environment, build flags, and working directory from the cache
+	// key, which should be a safe change as all relevant inputs into the type
+	// checking pass are already hashed into the key. This is temporarily guarded
+	// by an experiment because caching behavior is subtle and difficult to
+	// comprehensively test.
+	ExperimentalPackageCacheKey bool
 }
 
 // DebuggingOptions should not affect the logical execution of Gopls, but may
@@ -550,6 +559,7 @@
 func (o *Options) enableAllExperiments() {
 	o.ExperimentalDiagnosticsDelay = 200 * time.Millisecond
 	o.ExperimentalWorkspaceModule = true
+	o.ExperimentalPackageCacheKey = true
 	o.Staticcheck = true
 	o.Gofumpt = true
 	o.SymbolStyle = DynamicSymbols