Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: inlay hints for auto-implicits #6768

Merged
merged 6 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Init/Prelude.lean
Original file line number Diff line number Diff line change
Expand Up @@ -2503,6 +2503,9 @@ instance (p₁ p₂ : String.Pos) : Decidable (LE.le p₁ p₂) :=
instance (p₁ p₂ : String.Pos) : Decidable (LT.lt p₁ p₂) :=
inferInstanceAs (Decidable (LT.lt p₁.byteIdx p₂.byteIdx))

instance : Min String.Pos := minOfLe
instance : Max String.Pos := maxOfLe

/-- A `String.Pos` pointing at the end of this string. -/
@[inline] def String.endPos (s : String) : String.Pos where
byteIdx := utf8ByteSize s
Expand Down
4 changes: 4 additions & 0 deletions src/Lean/Data/Lsp/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -426,5 +426,9 @@ structure WorkDoneProgressOptions where
workDoneProgress := false
deriving ToJson, FromJson

structure ResolveSupport where
properties : Array String
deriving FromJson, ToJson

end Lsp
end Lean
2 changes: 2 additions & 0 deletions src/Lean/Data/Lsp/Capabilities.lean
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ structure CompletionClientCapabilities where
structure TextDocumentClientCapabilities where
completion? : Option CompletionClientCapabilities := none
codeAction? : Option CodeActionClientCapabilities := none
inlayHint? : Option InlayHintClientCapabilities := none
deriving ToJson, FromJson

structure ShowDocumentClientCapabilities where
Expand Down Expand Up @@ -81,6 +82,7 @@ structure ServerCapabilities where
foldingRangeProvider : Bool := false
semanticTokensProvider? : Option SemanticTokensOptions := none
codeActionProvider? : Option CodeActionOptions := none
inlayHintProvider? : Option InlayHintOptions := none
deriving ToJson, FromJson

end Lsp
Expand Down
4 changes: 0 additions & 4 deletions src/Lean/Data/Lsp/CodeActions.lean
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,6 @@ structure CodeAction extends WorkDoneProgressParams, PartialResultParams where
data? : Option Json := none
deriving ToJson, FromJson

structure ResolveSupport where
properties : Array String
deriving FromJson, ToJson

structure CodeActionLiteralSupportValueSet where
/-- The code action kind values the client supports. When this
property exists the client also guarantees that it will
Expand Down
77 changes: 77 additions & 0 deletions src/Lean/Data/Lsp/LanguageFeatures.lean
Original file line number Diff line number Diff line change
Expand Up @@ -445,5 +445,82 @@ structure RenameParams extends TextDocumentPositionParams where
structure PrepareRenameParams extends TextDocumentPositionParams
deriving FromJson, ToJson

structure InlayHintParams extends WorkDoneProgressParams where
textDocument : TextDocumentIdentifier
range : Range
deriving FromJson, ToJson

inductive InlayHintTooltip
| plaintext (text : String)
| markdown (markup : MarkupContent)

instance : FromJson InlayHintTooltip where
fromJson?
| .str s => .ok <| .plaintext s
| j@(.obj _) => do return .markdown (← fromJson? j)
| j => .error s!"invalid inlay hint tooltip {j}"

instance : ToJson InlayHintTooltip where
toJson
| .plaintext text => toJson text
| .markdown markup => toJson markup

structure InlayHintLabelPart where
value : String
tooltip? : Option InlayHintTooltip := none
location? : Option Location := none
command? : Option Command := none
deriving FromJson, ToJson

inductive InlayHintLabel
| name (n : String)
| parts (p : Array InlayHintLabelPart)

instance : FromJson InlayHintLabel where
fromJson?
| .str s => .ok <| .name s
| j@(.arr _) => do return .parts (← fromJson? j)
| j => .error s!"invalid inlay hint label {j}"

instance : ToJson InlayHintLabel where
toJson
| .name n => toJson n
| .parts p => toJson p

inductive InlayHintKind where
| type
| parameter

instance : FromJson InlayHintKind where
fromJson?
| 1 => .ok .type
| 2 => .ok .parameter
| j => .error s!"unknown inlay hint kind {j}"

instance : ToJson InlayHintKind where
toJson
| .type => 1
| .parameter => 2

structure InlayHint where
position : Position
label : InlayHintLabel
kind? : Option InlayHintKind := none
textEdits? : Option (Array TextEdit) := none
tooltip? : Option (InlayHintTooltip) := none
paddingLeft? : Option Bool := none
paddingRight? : Option Bool := none
data? : Option Json := none
deriving FromJson, ToJson

structure InlayHintClientCapabilities where
dynamicRegistration? : Option Bool := none
resolveSupport? : Option ResolveSupport := none
deriving FromJson, ToJson

structure InlayHintOptions extends WorkDoneProgressOptions where
resolveProvider? : Option Bool := none
deriving FromJson, ToJson

end Lsp
end Lean
20 changes: 18 additions & 2 deletions src/Lean/Data/Lsp/Utf16.lean
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,31 @@ def utf8PosToLspPos (text : FileMap) (pos : String.Pos) : Lsp.Position :=
def utf8RangeToLspRange (text : FileMap) (range : String.Range) : Lsp.Range :=
{ start := text.utf8PosToLspPos range.start, «end» := text.utf8PosToLspPos range.stop }

def lspRangeToUtf8Range (text : FileMap) (range : Lsp.Range) : String.Range :=
{ start := text.lspPosToUtf8Pos range.start, stop := text.lspPosToUtf8Pos range.end }

end FileMap
end Lean

def DeclarationRange.ofFilePositions (text : FileMap) (pos : Position) (endPos : Position)
: DeclarationRange := {
pos,
charUtf16 := text.leanPosToLspPos pos |>.character
endPos,
endCharUtf16 := text.leanPosToLspPos endPos |>.character
}

def DeclarationRange.ofStringPositions (text : FileMap) (pos : String.Pos) (endPos : String.Pos)
: DeclarationRange :=
.ofFilePositions text (text.toPosition pos) (text.toPosition endPos)

/--
Convert the Lean `DeclarationRange` to an LSP `Range` by turning the 1-indexed line numbering into a
0-indexed line numbering and converting the character offset within the line to a UTF-16 indexed
offset.
-/
def Lean.DeclarationRange.toLspRange (r : Lean.DeclarationRange) : Lsp.Range := {
def DeclarationRange.toLspRange (r : DeclarationRange) : Lsp.Range := {
start := ⟨r.pos.line - 1, r.charUtf16⟩
«end» := ⟨r.endPos.line - 1, r.endCharUtf16⟩
}

end Lean
9 changes: 7 additions & 2 deletions src/Lean/Elab/BuiltinCommand.lean
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,16 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
return #[binder]

@[builtin_command_elab «variable»] def elabVariable : CommandElab
| `(variable $binders*) => do
| `(variable%$tk $binders*) => do
let binders ← binders.flatMapM replaceBinderAnnotation
-- Try to elaborate `binders` for sanity checking
runTermElabM fun _ => Term.withSynthesize <| Term.withAutoBoundImplicit <|
Term.elabBinders binders fun _ => pure ()
Term.elabBinders binders fun xs => do
-- Determine the set of auto-implicits for this variable command and add an inlay hint
-- for them. We will only actually add the auto-implicits to a type when the variables
-- declared here are used in some other declaration, but this is nonetheless the right
-- place to display the inlay hint.
let _ ← Term.addAutoBoundImplicits xs (tk.getTailPos? (canonicalOnly := true))
-- Remark: if we want to produce error messages when variables shadow existing ones, here is the place to do it.
for binder in binders do
let varUIds ← (← getBracketedBinderIds binder) |>.mapM (withFreshMacroScope ∘ MonadQuotation.addMacroScope)
Expand Down
3 changes: 2 additions & 1 deletion src/Lean/Elab/Declaration.lean
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,13 @@ def elabAxiom (modifiers : Modifiers) (stx : Syntax) : CommandElabM Unit := do
let scopeLevelNames ← Term.getLevelNames
let ⟨shortName, declName, allUserLevelNames⟩ ← Term.expandDeclId (← getCurrNamespace) scopeLevelNames declId modifiers
addDeclarationRangesForBuiltin declName modifiers.stx stx
Term.withAutoBoundImplicit do
mhuisi marked this conversation as resolved.
Show resolved Hide resolved
Term.withAutoBoundImplicitForbiddenPred (fun n => shortName == n) do
Term.withDeclName declName <| Term.withLevelNames allUserLevelNames <| Term.elabBinders binders.getArgs fun xs => do
Term.applyAttributesAt declName modifiers.attrs AttributeApplicationTime.beforeElaboration
let type ← Term.elabType typeStx
Term.synthesizeSyntheticMVarsNoPostponing
let xs ← Term.addAutoBoundImplicits xs
let xs ← Term.addAutoBoundImplicits xs (declId.getTailPos? (canonicalOnly := true))
let type ← instantiateMVars type
let type ← mkForallFVars xs type
let type ← mkForallFVars vars type (usedOnly := true)
Expand Down
10 changes: 1 addition & 9 deletions src/Lean/Elab/DeclarationRange.lean
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,7 @@ def getDeclarationRange? [Monad m] [MonadFileMap m] (stx : Syntax) : m (Option D
let some range := stx.getRange?
| return none
let fileMap ← getFileMap
--let range := fileMap.utf8RangeToLspRange
let pos := fileMap.toPosition range.start
let endPos := fileMap.toPosition range.stop
return some {
pos := pos
charUtf16 := fileMap.leanPosToLspPos pos |>.character
endPos := endPos
endCharUtf16 := fileMap.leanPosToLspPos endPos |>.character
}
return some <| .ofStringPositions fileMap range.start range.stop

/--
For most builtin declarations, the selection range is just its name, which is stored in the second position.
Expand Down
2 changes: 1 addition & 1 deletion src/Lean/Elab/DefView.lean
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def mkDefViewOfOpaque (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefV
def mkDefViewOfExample (modifiers : Modifiers) (stx : Syntax) : DefView :=
-- leading_parser "example " >> declSig >> declVal
let (binders, type) := expandOptDeclSig stx[1]
let id := mkIdentFrom stx `_example
let id := mkIdentFrom stx[0] `_example (canonical := true)
let declId := mkNode ``Parser.Command.declId #[id, mkNullNode]
{ ref := stx, headerRef := mkNullNode stx.getArgs[:2], kind := DefKind.example, modifiers := modifiers,
declId := declId, binders := binders, type? := type, value := stx[2] }
Expand Down
2 changes: 1 addition & 1 deletion src/Lean/Elab/Inductive.lean
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private def elabCtors (indFVars : Array Expr) (params : Array Expr) (r : ElabHea
return type
let type ← elabCtorType
Term.synthesizeSyntheticMVarsNoPostponing
let ctorParams ← Term.addAutoBoundImplicits ctorParams
let ctorParams ← Term.addAutoBoundImplicits ctorParams (ctorView.declId.getTailPos? (canonicalOnly := true))
let except (mvarId : MVarId) := ctorParams.any fun ctorParam => ctorParam.isMVar && ctorParam.mvarId! == mvarId
/-
We convert metavariables in the resulting type into extra parameters. Otherwise, we would not be able to elaborate
Expand Down
63 changes: 63 additions & 0 deletions src/Lean/Elab/InfoTree/InlayHints.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Marc Huisinga
-/
prelude
import Lean.Meta.Basic

namespace Lean.Elab

open Lean

structure InlayHintLinkLocation where
module : Name
range : String.Range

structure InlayHintLabelPart where
value : String
tooltip? : Option String := none
location? : Option InlayHintLinkLocation := none

inductive InlayHintLabel
| name (n : String)
| parts (p : Array InlayHintLabelPart)

inductive InlayHintKind where
| type
| parameter

structure InlayHintTextEdit where
range : String.Range
newText : String

structure InlayHintInfo where
position : String.Pos
label : InlayHintLabel
kind? : Option InlayHintKind := none
textEdits : Array InlayHintTextEdit := #[]
tooltip? : Option String := none
paddingLeft : Bool := false
paddingRight : Bool := false

structure InlayHint extends InlayHintInfo where
lctx : LocalContext
deferredResolution : InlayHintInfo → MetaM InlayHintInfo := fun i => .pure i
deriving TypeName

namespace InlayHint

def toCustomInfo (i : InlayHint) : CustomInfo := {
stx := .missing
value := .mk i
}

def ofCustomInfo? (c : CustomInfo) : Option InlayHint :=
c.value.get? InlayHint

def resolveDeferred (i : InlayHint) : MetaM InlayHint := do
let info := i.toInlayHintInfo
let info ← i.deferredResolution info
return { i with toInlayHintInfo := info }

end InlayHint
2 changes: 1 addition & 1 deletion src/Lean/Elab/MutualDef.lean
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ private def elabHeaders (views : Array DefView)
type ← cleanupOfNat type
let (binderIds, xs) := xs.unzip
-- TODO: add forbidden predicate using `shortDeclName` from `views`
let xs ← addAutoBoundImplicits xs
let xs ← addAutoBoundImplicits xs (view.declId.getTailPos? (canonicalOnly := true))
type ← mkForallFVars' xs type
type ← instantiateMVarsProfiling type
let levelNames ← getLevelNames
Expand Down
6 changes: 4 additions & 2 deletions src/Lean/Elab/MutualInductive.lean
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ private def elabHeadersAux (views : Array InductiveView) (i : Nat) (acc : Array
let typeStx ← view.type?.getDM `(Sort _)
let type ← Term.elabType typeStx
Term.synthesizeSyntheticMVarsNoPostponing
let indices ← Term.addAutoBoundImplicits #[]
let inlayHintPos? := view.binders.getTailPos? (canonicalOnly := true)
<|> view.declId.getTailPos? (canonicalOnly := true)
let indices ← Term.addAutoBoundImplicits #[] inlayHintPos?
let type ← mkForallFVars indices type
if view.allowIndices then
unless (← isTypeFormerType type) do
Expand All @@ -297,7 +299,7 @@ private def elabHeadersAux (views : Array InductiveView) (i : Nat) (acc : Array
unless (← whnfD type).isSort do
throwErrorAt typeStx "invalid resulting type, expecting 'Type _' or 'Prop'"
return (type, indices.size)
let params ← Term.addAutoBoundImplicits params
let params ← Term.addAutoBoundImplicits params (view.declId.getTailPos? (canonicalOnly := true))
trace[Elab.inductive] "header params: {params}, type: {type}"
let levelNames ← Term.getLevelNames
return acc.push { lctx := (← getLCtx), localInsts := (← getLocalInstances), levelNames, params, type, view }
Expand Down
4 changes: 2 additions & 2 deletions src/Lean/Elab/Structure.lean
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ private def elabFieldTypeValue (view : StructFieldView) : TermElabM (Option Expr
| some valStx =>
Term.synthesizeSyntheticMVarsNoPostponing
-- TODO: add forbidden predicate using `shortDeclName` from `view`
let params ← Term.addAutoBoundImplicits params
let params ← Term.addAutoBoundImplicits params (view.nameId.getTailPos? (canonicalOnly := true))
let value ← Term.withoutAutoBoundImplicit <| Term.elabTerm valStx none
registerFailedToInferFieldType view.name (← inferType value) view.nameId
registerFailedToInferDefaultValue view.name value valStx
Expand All @@ -572,7 +572,7 @@ private def elabFieldTypeValue (view : StructFieldView) : TermElabM (Option Expr
let type ← Term.elabType typeStx
registerFailedToInferFieldType view.name type typeStx
Term.synthesizeSyntheticMVarsNoPostponing
let params ← Term.addAutoBoundImplicits params
let params ← Term.addAutoBoundImplicits params (view.nameId.getTailPos? (canonicalOnly := true))
match view.value? with
| none =>
let type ← mkForallFVars params type
Expand Down
Loading
Loading