internal/lsp: add settings for inlay hints and enable

This change adds user settings for enabling inlay hints, modeled
roughly after analyzers. This will allow users to turn on specific
inlay hints that they like and leave others off.

With all of the inlay hints turned off by default, we can now enable
inlay hints.

Change-Id: Ie5dfcbbab1e0b7312eafcc4aa08cb4fe8a83fc31
Reviewed-on: https://go-review.googlesource.com/c/tools/+/411906
Run-TryBot: Suzy Mueller <suzmue@golang.org>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/doc/generate.go b/gopls/doc/generate.go
index e63653d..c7e0e0f 100644
--- a/gopls/doc/generate.go
+++ b/gopls/doc/generate.go
@@ -63,6 +63,9 @@
 	if ok, err := rewriteFile(filepath.Join(baseDir, "gopls/doc/analyzers.md"), api, write, rewriteAnalyzers); !ok || err != nil {
 		return ok, err
 	}
+	if ok, err := rewriteFile(filepath.Join(baseDir, "gopls/doc/inlayHints.md"), api, write, rewriteInlayHints); !ok || err != nil {
+		return ok, err
+	}
 
 	return true, nil
 }
@@ -102,6 +105,7 @@
 	} {
 		api.Analyzers = append(api.Analyzers, loadAnalyzers(m)...)
 	}
+	api.Hints = loadHints(source.AllInlayHints)
 	for _, category := range []reflect.Value{
 		reflect.ValueOf(defaults.UserOptions),
 	} {
@@ -146,6 +150,14 @@
 						Default: def,
 					})
 				}
+			case "hints":
+				for _, a := range api.Hints {
+					opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, source.EnumKey{
+						Name:    fmt.Sprintf("%q", a.Name),
+						Doc:     a.Doc,
+						Default: strconv.FormatBool(a.Default),
+					})
+				}
 			}
 		}
 	}
@@ -488,6 +500,23 @@
 	return json
 }
 
+func loadHints(m map[string]*source.Hint) []*source.HintJSON {
+	var sorted []string
+	for _, h := range m {
+		sorted = append(sorted, h.Name)
+	}
+	sort.Strings(sorted)
+	var json []*source.HintJSON
+	for _, name := range sorted {
+		h := m[name]
+		json = append(json, &source.HintJSON{
+			Name: h.Name,
+			Doc:  h.Doc,
+		})
+	}
+	return json
+}
+
 func lowerFirst(x string) string {
 	if x == "" {
 		return x
@@ -699,6 +728,21 @@
 	return replaceSection(doc, "Analyzers", section.Bytes())
 }
 
+func rewriteInlayHints(doc []byte, api *source.APIJSON) ([]byte, error) {
+	section := bytes.NewBuffer(nil)
+	for _, hint := range api.Hints {
+		fmt.Fprintf(section, "## **%v**\n\n", hint.Name)
+		fmt.Fprintf(section, "%s\n\n", hint.Doc)
+		switch hint.Default {
+		case true:
+			fmt.Fprintf(section, "**Enabled by default.**\n\n")
+		case false:
+			fmt.Fprintf(section, "**Disabled by default. Enable it by setting `\"hints\": {\"%s\": true}`.**\n\n", hint.Name)
+		}
+	}
+	return replaceSection(doc, "Hints", section.Bytes())
+}
+
 func replaceSection(doc []byte, sectionName string, replacement []byte) ([]byte, error) {
 	re := regexp.MustCompile(fmt.Sprintf(`(?s)<!-- BEGIN %v.* -->\n(.*?)<!-- END %v.* -->`, sectionName, sectionName))
 	idx := re.FindSubmatchIndex(doc)
diff --git a/gopls/doc/inlayHints.md b/gopls/doc/inlayHints.md
new file mode 100644
index 0000000..a4fd3e5
--- /dev/null
+++ b/gopls/doc/inlayHints.md
@@ -0,0 +1,73 @@
+# Hints
+
+This document describes the inlay hints that `gopls` uses inside the editor.
+
+<!-- BEGIN Hints: DO NOT MANUALLY EDIT THIS SECTION -->
+## **assign_variable_types**
+
+Enable/disable inlay hints for variable types in assign statements:
+
+	i/* int/*, j/* int/* := 0, len(r)-1
+
+**Disabled by default. Enable it by setting `"hints": {"assign_variable_types": true}`.**
+
+## **composite_literal_fields**
+
+Enable/disable inlay hints for composite literal field names:
+
+	{in: "Hello, world", want: "dlrow ,olleH"}
+
+**Disabled by default. Enable it by setting `"hints": {"composite_literal_fields": true}`.**
+
+## **composite_literal_types**
+
+Enable/disable inlay hints for composite literal types:
+
+	for _, c := range []struct {
+		in, want string
+	}{
+		/*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"},
+	}
+
+**Disabled by default. Enable it by setting `"hints": {"composite_literal_types": true}`.**
+
+## **constant_values**
+
+Enable/disable inlay hints for constant values:
+
+	const (
+		KindNone   Kind = iota/* = 0*/
+		KindPrint/*  = 1*/
+		KindPrintf/* = 2*/
+		KindErrorf/* = 3*/
+	)
+
+**Disabled by default. Enable it by setting `"hints": {"constant_values": true}`.**
+
+## **function_type_parameters**
+
+Enable/disable inlay hints for implicit type parameters on generic functions:
+
+	myFoo/*[int, string]*/(1, "hello")
+
+**Disabled by default. Enable it by setting `"hints": {"function_type_parameters": true}`.**
+
+## **parameter_names**
+
+Enable/disable inlay hints for parameter names:
+
+	parseInt(/* str: */ "123", /* radix: */ 8)
+
+**Disabled by default. Enable it by setting `"hints": {"parameter_names": true}`.**
+
+## **range_variable_types**
+
+Enable/disable inlay hints for variable types in range statements:
+
+	for k/* int*/, v/* string/* := range []string{} {
+		fmt.Println(k, v)
+	}
+
+**Disabled by default. Enable it by setting `"hints": {"range_variable_types": true}`.**
+
+<!-- END Hints: DO NOT MANUALLY EDIT THIS SECTION -->
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index 092a3c7..0ed0e19 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -35,6 +35,7 @@
   * [Completion](#completion)
   * [Diagnostic](#diagnostic)
   * [Documentation](#documentation)
+  * [Inlayhint](#inlayhint)
   * [Navigation](#navigation)
 
 ### Build
@@ -370,6 +371,18 @@
 
 Default: `true`.
 
+#### Inlayhint
+
+##### **hints** *map[string]bool*
+
+**This setting is experimental and may be deleted.**
+
+hints specify inlay hints that users want to see.
+A full list of hints that gopls uses can be found
+[here](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).
+
+Default: `{}`.
+
 #### Navigation
 
 ##### **importShortcut** *enum*
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 478152b..385a04a 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -153,6 +153,7 @@
 			HoverProvider:             true,
 			DocumentHighlightProvider: true,
 			DocumentLinkProvider:      protocol.DocumentLinkOptions{},
+			InlayHintProvider:         protocol.InlayHintOptions{},
 			ReferencesProvider:        true,
 			RenameProvider:            renameOpts,
 			SignatureHelpProvider: protocol.SignatureHelpOptions{
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 56356e9..2ec833b 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -67,6 +67,9 @@
 	tests.EnableAllAnalyzers(view, options)
 	view.SetOptions(ctx, options)
 
+	// Enable all inlay hints for tests.
+	tests.EnableAllInlayHints(view, options)
+
 	// Only run the -modfile specific tests in module mode with Go 1.14 or above.
 	datum.ModfileFlagAvailable = len(snapshot.ModFiles()) > 0 && testenv.Go1Point() >= 14
 	release()
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index 0695efc..4188d9d 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -506,6 +506,51 @@
 				Hierarchy: "ui.diagnostic",
 			},
 			{
+				Name: "hints",
+				Type: "map[string]bool",
+				Doc:  "hints specify inlay hints that users want to see.\nA full list of hints that gopls uses can be found\n[here](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).\n",
+				EnumKeys: EnumKeys{Keys: []EnumKey{
+					{
+						Name:    "\"assign_variable_types\"",
+						Doc:     "Enable/disable inlay hints for variable types in assign statements:\n\n\ti/* int/*, j/* int/* := 0, len(r)-1",
+						Default: "false",
+					},
+					{
+						Name:    "\"composite_literal_fields\"",
+						Doc:     "Enable/disable inlay hints for composite literal field names:\n\n\t{in: \"Hello, world\", want: \"dlrow ,olleH\"}",
+						Default: "false",
+					},
+					{
+						Name:    "\"composite_literal_types\"",
+						Doc:     "Enable/disable inlay hints for composite literal types:\n\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}",
+						Default: "false",
+					},
+					{
+						Name:    "\"constant_values\"",
+						Doc:     "Enable/disable inlay hints for constant values:\n\n\tconst (\n\t\tKindNone   Kind = iota/* = 0*/\n\t\tKindPrint/*  = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)",
+						Default: "false",
+					},
+					{
+						Name:    "\"function_type_parameters\"",
+						Doc:     "Enable/disable inlay hints for implicit type parameters on generic functions:\n\n\tmyFoo/*[int, string]*/(1, \"hello\")",
+						Default: "false",
+					},
+					{
+						Name:    "\"parameter_names\"",
+						Doc:     "Enable/disable inlay hints for parameter names:\n\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)",
+						Default: "false",
+					},
+					{
+						Name:    "\"range_variable_types\"",
+						Doc:     "Enable/disable inlay hints for variable types in range statements:\n\n\tfor k/* int*/, v/* string/* := range []string{} {\n\t\tfmt.Println(k, v)\n\t}",
+						Default: "false",
+					},
+				}},
+				Default:   "{}",
+				Status:    "experimental",
+				Hierarchy: "ui.inlayhint",
+			},
+			{
 				Name: "codelenses",
 				Type: "map[string]bool",
 				Doc:  "codelenses overrides the enabled/disabled state of code lenses. See the\n\"Code Lenses\" section of the\n[Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#code-lenses)\nfor the list of supported lenses.\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n  \"codelenses\": {\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",
@@ -979,4 +1024,34 @@
 			Default: true,
 		},
 	},
+	Hints: []*HintJSON{
+		{
+			Name: "assign_variable_types",
+			Doc:  "Enable/disable inlay hints for variable types in assign statements:\n\n\ti/* int/*, j/* int/* := 0, len(r)-1",
+		},
+		{
+			Name: "composite_literal_fields",
+			Doc:  "Enable/disable inlay hints for composite literal field names:\n\n\t{in: \"Hello, world\", want: \"dlrow ,olleH\"}",
+		},
+		{
+			Name: "composite_literal_types",
+			Doc:  "Enable/disable inlay hints for composite literal types:\n\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}",
+		},
+		{
+			Name: "constant_values",
+			Doc:  "Enable/disable inlay hints for constant values:\n\n\tconst (\n\t\tKindNone   Kind = iota/* = 0*/\n\t\tKindPrint/*  = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)",
+		},
+		{
+			Name: "function_type_parameters",
+			Doc:  "Enable/disable inlay hints for implicit type parameters on generic functions:\n\n\tmyFoo/*[int, string]*/(1, \"hello\")",
+		},
+		{
+			Name: "parameter_names",
+			Doc:  "Enable/disable inlay hints for parameter names:\n\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)",
+		},
+		{
+			Name: "range_variable_types",
+			Doc:  "Enable/disable inlay hints for variable types in range statements:\n\n\tfor k/* int*/, v/* string/* := range []string{} {\n\t\tfmt.Println(k, v)\n\t}",
+		},
+	},
 }
diff --git a/internal/lsp/source/inlay_hint.go b/internal/lsp/source/inlay_hint.go
index 8369681..99e1ad0 100644
--- a/internal/lsp/source/inlay_hint.go
+++ b/internal/lsp/source/inlay_hint.go
@@ -23,6 +23,87 @@
 	maxLabelLength = 28
 )
 
+type InlayHintFunc func(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint
+
+type Hint struct {
+	Name string
+	Doc  string
+	Run  InlayHintFunc
+}
+
+const (
+	ParameterNames             = "parameter_names"
+	AssignVariableTypes        = "assign_variable_types"
+	ConstantValues             = "constant_values"
+	RangeVariableTypes         = "range_variable_types"
+	CompositeLiteralTypes      = "composite_literal_types"
+	CompositeLiteralFieldNames = "composite_literal_fields"
+	FunctionTypeParameters     = "function_type_parameters"
+)
+
+var AllInlayHints = map[string]*Hint{
+	AssignVariableTypes: {
+		Name: AssignVariableTypes,
+		Doc: `Enable/disable inlay hints for variable types in assign statements:
+
+	i/* int/*, j/* int/* := 0, len(r)-1`,
+		Run: assignVariableTypes,
+	},
+	ParameterNames: {
+		Name: ParameterNames,
+		Doc: `Enable/disable inlay hints for parameter names:
+
+	parseInt(/* str: */ "123", /* radix: */ 8)`,
+		Run: parameterNames,
+	},
+	ConstantValues: {
+		Name: ConstantValues,
+		Doc: `Enable/disable inlay hints for constant values:
+
+	const (
+		KindNone   Kind = iota/* = 0*/
+		KindPrint/*  = 1*/
+		KindPrintf/* = 2*/
+		KindErrorf/* = 3*/
+	)`,
+		Run: constantValues,
+	},
+	RangeVariableTypes: {
+		Name: RangeVariableTypes,
+		Doc: `Enable/disable inlay hints for variable types in range statements:
+
+	for k/* int*/, v/* string/* := range []string{} {
+		fmt.Println(k, v)
+	}`,
+		Run: rangeVariableTypes,
+	},
+	CompositeLiteralTypes: {
+		Name: CompositeLiteralTypes,
+		Doc: `Enable/disable inlay hints for composite literal types:
+
+	for _, c := range []struct {
+		in, want string
+	}{
+		/*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"},
+	}`,
+		Run: compositeLiteralTypes,
+	},
+	CompositeLiteralFieldNames: {
+		Name: CompositeLiteralFieldNames,
+		Doc: `Enable/disable inlay hints for composite literal field names:
+
+	{in: "Hello, world", want: "dlrow ,olleH"}`,
+		Run: compositeLiteralFields,
+	},
+	FunctionTypeParameters: {
+		Name: FunctionTypeParameters,
+		Doc: `Enable/disable inlay hints for implicit type parameters on generic functions:
+
+	myFoo/*[int, string]*/(1, "hello")`,
+		Run: funcTypeParams,
+	},
+}
+
 func InlayHint(ctx context.Context, snapshot Snapshot, fh FileHandle, _ protocol.Range) ([]protocol.InlayHint, error) {
 	ctx, done := event.Start(ctx, "source.InlayHint")
 	defer done()
@@ -32,38 +113,47 @@
 		return nil, fmt.Errorf("getting file for InlayHint: %w", err)
 	}
 
+	// Collect a list of the inlay hints that are enabled.
+	inlayHintOptions := snapshot.View().Options().InlayHintOptions
+	var enabledHints []InlayHintFunc
+	for hint, enabled := range inlayHintOptions.Hints {
+		if !enabled {
+			continue
+		}
+		if h, ok := AllInlayHints[hint]; ok {
+			enabledHints = append(enabledHints, h.Run)
+		}
+	}
+	if len(enabledHints) == 0 {
+		return nil, nil
+	}
+
 	tmap := lsppos.NewTokenMapper(pgf.Src, pgf.Tok)
 	info := pkg.GetTypesInfo()
 	q := Qualifier(pgf.File, pkg.GetTypes(), info)
 
 	var hints []protocol.InlayHint
 	ast.Inspect(pgf.File, func(node ast.Node) bool {
-		switch n := node.(type) {
-		case *ast.CallExpr:
-			hints = append(hints, parameterNames(n, tmap, info)...)
-			hints = append(hints, funcTypeParams(n, tmap, info)...)
-		case *ast.AssignStmt:
-			hints = append(hints, assignVariableTypes(n, tmap, info, &q)...)
-		case *ast.RangeStmt:
-			hints = append(hints, rangeVariableTypes(n, tmap, info, &q)...)
-		case *ast.GenDecl:
-			hints = append(hints, constantValues(n, tmap, info)...)
-		case *ast.CompositeLit:
-			hints = append(hints, compositeLiterals(n, tmap, info, &q)...)
+		for _, fn := range enabledHints {
+			hints = append(hints, fn(node, tmap, info, &q)...)
 		}
 		return true
 	})
 	return hints, nil
 }
 
-func parameterNames(node *ast.CallExpr, tmap *lsppos.TokenMapper, info *types.Info) []protocol.InlayHint {
-	signature, ok := info.TypeOf(node.Fun).(*types.Signature)
+func parameterNames(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, _ *types.Qualifier) []protocol.InlayHint {
+	callExpr, ok := node.(*ast.CallExpr)
+	if !ok {
+		return nil
+	}
+	signature, ok := info.TypeOf(callExpr.Fun).(*types.Signature)
 	if !ok {
 		return nil
 	}
 
 	var hints []protocol.InlayHint
-	for i, v := range node.Args {
+	for i, v := range callExpr.Args {
 		start, ok := tmap.Position(v.Pos())
 		if !ok {
 			continue
@@ -92,8 +182,12 @@
 	return hints
 }
 
-func funcTypeParams(node *ast.CallExpr, tmap *lsppos.TokenMapper, info *types.Info) []protocol.InlayHint {
-	id, ok := node.Fun.(*ast.Ident)
+func funcTypeParams(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, _ *types.Qualifier) []protocol.InlayHint {
+	ce, ok := node.(*ast.CallExpr)
+	if !ok {
+		return nil
+	}
+	id, ok := ce.Fun.(*ast.Ident)
 	if !ok {
 		return nil
 	}
@@ -119,12 +213,14 @@
 	}}
 }
 
-func assignVariableTypes(node *ast.AssignStmt, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
-	if node.Tok != token.DEFINE {
+func assignVariableTypes(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
+	stmt, ok := node.(*ast.AssignStmt)
+	if !ok || stmt.Tok != token.DEFINE {
 		return nil
 	}
+
 	var hints []protocol.InlayHint
-	for _, v := range node.Lhs {
+	for _, v := range stmt.Lhs {
 		if h := variableType(v, tmap, info, q); h != nil {
 			hints = append(hints, *h)
 		}
@@ -132,12 +228,16 @@
 	return hints
 }
 
-func rangeVariableTypes(node *ast.RangeStmt, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
+func rangeVariableTypes(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
+	rStmt, ok := node.(*ast.RangeStmt)
+	if !ok {
+		return nil
+	}
 	var hints []protocol.InlayHint
-	if h := variableType(node.Key, tmap, info, q); h != nil {
+	if h := variableType(rStmt.Key, tmap, info, q); h != nil {
 		hints = append(hints, *h)
 	}
-	if h := variableType(node.Value, tmap, info, q); h != nil {
+	if h := variableType(rStmt.Value, tmap, info, q); h != nil {
 		hints = append(hints, *h)
 	}
 	return hints
@@ -160,13 +260,14 @@
 	}
 }
 
-func constantValues(node *ast.GenDecl, tmap *lsppos.TokenMapper, info *types.Info) []protocol.InlayHint {
-	if node.Tok != token.CONST {
+func constantValues(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, _ *types.Qualifier) []protocol.InlayHint {
+	genDecl, ok := node.(*ast.GenDecl)
+	if !ok || genDecl.Tok != token.CONST {
 		return nil
 	}
 
 	var hints []protocol.InlayHint
-	for _, v := range node.Specs {
+	for _, v := range genDecl.Specs {
 		spec, ok := v.(*ast.ValueSpec)
 		if !ok {
 			continue
@@ -210,36 +311,26 @@
 	return hints
 }
 
-func compositeLiterals(node *ast.CompositeLit, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
-	typ := info.TypeOf(node)
+func compositeLiteralFields(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
+	compLit, ok := node.(*ast.CompositeLit)
+	if !ok {
+		return nil
+	}
+	typ := info.TypeOf(compLit)
 	if typ == nil {
 		return nil
 	}
-
-	prefix := ""
 	if t, ok := typ.(*types.Pointer); ok {
 		typ = t.Elem()
-		prefix = "&"
 	}
-
 	strct, ok := typ.Underlying().(*types.Struct)
 	if !ok {
 		return nil
 	}
 
 	var hints []protocol.InlayHint
-	if node.Type == nil {
-		// The type for this struct is implicit, add an inlay hint.
-		if start, ok := tmap.Position(node.Lbrace); ok {
-			hints = append(hints, protocol.InlayHint{
-				Position: &start,
-				Label:    buildLabel(fmt.Sprintf("%s%s", prefix, types.TypeString(typ, *q))),
-				Kind:     protocol.Type,
-			})
-		}
-	}
 
-	for i, v := range node.Elts {
+	for i, v := range compLit.Elts {
 		if _, ok := v.(*ast.KeyValueExpr); !ok {
 			start, ok := tmap.Position(v.Pos())
 			if !ok {
@@ -259,6 +350,35 @@
 	return hints
 }
 
+func compositeLiteralTypes(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
+	compLit, ok := node.(*ast.CompositeLit)
+	if !ok {
+		return nil
+	}
+	typ := info.TypeOf(compLit)
+	if typ == nil {
+		return nil
+	}
+	if compLit.Type != nil {
+		return nil
+	}
+	prefix := ""
+	if t, ok := typ.(*types.Pointer); ok {
+		typ = t.Elem()
+		prefix = "&"
+	}
+	// The type for this composite literal is implicit, add an inlay hint.
+	start, ok := tmap.Position(compLit.Lbrace)
+	if !ok {
+		return nil
+	}
+	return []protocol.InlayHint{{
+		Position: &start,
+		Label:    buildLabel(fmt.Sprintf("%s%s", prefix, types.TypeString(typ, *q))),
+		Kind:     protocol.Type,
+	}}
+}
+
 func buildLabel(s string) []protocol.InlayHintLabelPart {
 	label := protocol.InlayHintLabelPart{
 		Value: s,
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index d1d34ef..5da14eb 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -130,6 +130,7 @@
 							Nil:    true,
 						},
 					},
+					InlayHintOptions: InlayHintOptions{},
 					DocumentationOptions: DocumentationOptions{
 						HoverKind:    FullDocumentation,
 						LinkTarget:   "pkg.go.dev",
@@ -289,6 +290,7 @@
 	CompletionOptions
 	NavigationOptions
 	DiagnosticOptions
+	InlayHintOptions
 
 	// Codelenses overrides the enabled/disabled state of code lenses. See the
 	// "Code Lenses" section of the
@@ -407,6 +409,13 @@
 	ExperimentalWatchedFileDelay time.Duration `status:"experimental"`
 }
 
+type InlayHintOptions struct {
+	// Hints specify inlay hints that users want to see.
+	// A full list of hints that gopls uses can be found
+	// [here](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).
+	Hints map[string]bool `status:"experimental"`
+}
+
 type NavigationOptions struct {
 	// ImportShortcut specifies whether import statements should link to
 	// documentation or go to definitions.
@@ -915,6 +924,9 @@
 	case "analyses":
 		result.setBoolMap(&o.Analyses)
 
+	case "hints":
+		result.setBoolMap(&o.Hints)
+
 	case "annotations":
 		result.setAnnotationMap(&o.Annotations)
 
@@ -1351,6 +1363,7 @@
 	Commands  []*CommandJSON
 	Lenses    []*LensJSON
 	Analyzers []*AnalyzerJSON
+	Hints     []*HintJSON
 }
 
 type OptionJSON struct {
@@ -1416,12 +1429,8 @@
 }
 
 func shouldShowEnumKeysInSettings(name string) bool {
-	// Both of these fields have too many possible options to print.
-	return !hardcodedEnumKeys(name)
-}
-
-func hardcodedEnumKeys(name string) bool {
-	return name == "analyses" || name == "codelenses"
+	// These fields have too many possible options to print.
+	return !(name == "analyses" || name == "codelenses" || name == "hints")
 }
 
 type EnumKeys struct {
@@ -1489,3 +1498,17 @@
 func (a *AnalyzerJSON) Write(w io.Writer) {
 	fmt.Fprintf(w, "%s (%s): %v", a.Name, a.Doc, a.Default)
 }
+
+type HintJSON struct {
+	Name    string
+	Doc     string
+	Default bool
+}
+
+func (h *HintJSON) String() string {
+	return h.Name
+}
+
+func (h *HintJSON) Write(w io.Writer) {
+	fmt.Fprintf(w, "%s (%s): %v", h.Name, h.Doc, h.Default)
+}
diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go
index 11dda1f..98562d6 100644
--- a/internal/lsp/tests/util.go
+++ b/internal/lsp/tests/util.go
@@ -512,6 +512,15 @@
 	}
 }
 
+func EnableAllInlayHints(view source.View, opts *source.Options) {
+	if opts.Hints == nil {
+		opts.Hints = make(map[string]bool)
+	}
+	for name := range source.AllInlayHints {
+		opts.Hints[name] = true
+	}
+}
+
 func WorkspaceSymbolsString(ctx context.Context, data *Data, queryURI span.URI, symbols []protocol.SymbolInformation) (string, error) {
 	queryDir := filepath.Dir(queryURI.Filename())
 	var filtered []string