Skip to content

Commit 69d8d63

Browse files
authored
feat: hint about inaccessible private declaration on dot notation failure (#10803)
This PR improves the error message of generalized field notation if the issue is that the resolved declaration is not visible in the current context.
1 parent dc7c184 commit 69d8d63

File tree

4 files changed

+55
-16
lines changed

4 files changed

+55
-16
lines changed

src/Lean/Elab/App.lean

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,8 +1252,11 @@ inductive LValResolution where
12521252
The `baseName` is the base name of the type to search for in the parameter list. -/
12531253
| localRec (baseName : Name) (fvar : Expr)
12541254

1255+
private def mkLValError (e : Expr) (eType : Expr) (msg : MessageData) : MessageData :=
1256+
m!"{msg}{indentExpr e}\nhas type{indentExpr eType}"
1257+
12551258
private def throwLValErrorAt (ref : Syntax) (e : Expr) (eType : Expr) (msg : MessageData) : TermElabM α :=
1256-
throwErrorAt ref "{msg}{indentExpr e}\nhas type{indentExpr eType}"
1259+
throwErrorAt ref (mkLValError e eType msg)
12571260

12581261
private def throwLValError (e : Expr) (eType : Expr) (msg : MessageData) : TermElabM α := do
12591262
throwLValErrorAt (← getRef) e eType msg
@@ -1322,9 +1325,19 @@ where
13221325
| some (_, p2) => prodArity p2 + 1
13231326

13241327
private def resolveLValAux (e : Expr) (eType : Expr) (lval : LVal) : TermElabM LValResolution := do
1325-
let throwInvalidFieldAt {α : Type} (ref : Syntax) (fieldName : String) (fullName : Name) : TermElabM α := do
1326-
throwLValErrorAt ref e eType <| ← mkUnknownIdentifierMessage (declHint := fullName)
1327-
m!"Invalid field `{fieldName}`: The environment does not contain `{fullName}`"
1328+
let throwInvalidFieldAt {α : Type} (ref : Syntax) (fieldName : String) (fullName : Name)
1329+
(declHint := Name.anonymous) : TermElabM α := do
1330+
let msg ←
1331+
-- ordering: put decl hint, if any, last
1332+
mkUnknownIdentifierMessage (declHint := declHint)
1333+
(mkLValError e eType
1334+
m!"Invalid field `{fieldName}`: The environment does not contain `{fullName}`")
1335+
-- HACK: Simulate previous embedding of tagged `mkUnknownIdentifierMessage`.
1336+
-- The "import unknown identifier" code action requires the tag to be present somewhere in the
1337+
-- message. But if it is at the root, the tag will also be shown to the user even though the
1338+
-- current help page does not address field notation, which should likely get its own help page
1339+
-- (if any).
1340+
throwErrorAt ref m!"{msg}{.nil}"
13281341
if eType.isForall then
13291342
match lval with
13301343
| LVal.fieldName _ fieldName suffix? fullRef =>
@@ -1390,6 +1403,9 @@ private def resolveLValAux (e : Expr) (eType : Expr) (lval : LVal) : TermElabM L
13901403
if let some (baseStructName, fullName) ← findMethod? structName (.mkSimple fieldName) then
13911404
return LValResolution.const baseStructName structName fullName
13921405
throwInvalidFieldAt fullRef fieldName fullName
1406+
-- Suggest a potential unreachable private name as hint. This does not cover structure
1407+
-- inheritance, nor `import all`.
1408+
(declHint := (mkPrivateName env structName).mkStr fieldName)
13931409
| none, LVal.fieldName _ _ (some suffix) fullRef =>
13941410
-- This may be a function constant whose implicit arguments have already been filled in:
13951411
let c := e.getAppFn

src/Lean/Elab/Term/TermElabM.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1971,7 +1971,7 @@ where
19711971
let env ← getEnv
19721972
-- check for scope errors before trying auto implicits
19731973
if env.isExporting then
1974-
if let [(npriv, _)] ← withEnv (env.setExporting false) <| resolveGlobalName (enableLog := false) n then
1974+
if let [(npriv, _)] ← withoutExporting <| resolveGlobalName (enableLog := false) n then
19751975
throwUnknownIdentifierAt (declHint := npriv) stx m!"Unknown identifier `{.ofConstName n}`"
19761976
if (← read).autoBoundImplicit &&
19771977
!(← read).autoBoundImplicitForbidden n &&

src/Lean/Exception.lean

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,8 @@ function directly.
105105
protected def «throwNamedErrorAt» [Monad m] [MonadError m] (ref : Syntax) (name : Name) (msg : MessageData) : m α :=
106106
withRef ref <| Lean.throwNamedError name msg
107107

108-
/--
109-
Creates a `MessageData` that is tagged with `unknownIdentifierMessageTag`.
110-
This tag is used by the 'import unknown identifier' code action to detect messages that should
111-
prompt the code action.
112-
The end position of the range of an unknown identifier message should always point at the end of the
113-
unknown identifier.
114-
115-
If `declHint` is specified, a corresponding hint is added to the message in case the name refers to
116-
a private declaration that is not accessible in the current context.
117-
-/
118-
def mkUnknownIdentifierMessage [Monad m] [MonadEnv m] [MonadError m] (msg : MessageData)
108+
/-- Like `mkUnknownIdentifierMessage`, but does not tag the message. -/
109+
def mkUnknownIdentifierMessageCore [Monad m] [MonadEnv m] [MonadError m] (msg : MessageData)
119110
(declHint := Name.anonymous) : m MessageData := do
120111
let mut msg := msg
121112
let env ← getEnv
@@ -131,6 +122,21 @@ def mkUnknownIdentifierMessage [Monad m] [MonadEnv m] [MonadError m] (msg : Mess
131122
msg ++ .note m!"A private declaration `{c}` (from `{mod}`) exists but would need to be public to access here."
132123
else
133124
msg ++ .note m!"A public declaration `{c}` exists but is imported privately; consider adding `public import {mod}`."
125+
return msg
126+
127+
/--
128+
Creates a `MessageData` that is tagged with `unknownIdentifierMessageTag`.
129+
This tag is used by the 'import unknown identifier' code action to detect messages that should
130+
prompt the code action.
131+
The end position of the range of an unknown identifier message should always point at the end of the
132+
unknown identifier.
133+
134+
If `declHint` is specified, a corresponding hint is added to the message in case the name refers to
135+
a private declaration that is not accessible in the current context.
136+
-/
137+
def mkUnknownIdentifierMessage [Monad m] [MonadEnv m] [MonadError m] (msg : MessageData)
138+
(declHint := Name.anonymous) : m MessageData := do
139+
let msg ← mkUnknownIdentifierMessageCore msg declHint
134140
return MessageData.tagged unknownIdentifierMessageTag msg
135141

136142
/--

tests/pkg/module/Module/Basic.lean

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,20 @@ attribute [simp] f_struct
479479
/-! `initialize` should be run even if imported IR-only. -/
480480
481481
public initialize initialized : Nat ← pure 5
482+
483+
/-! Error message on private dot notation access. -/
484+
485+
public structure S
486+
487+
def S.s := 1
488+
489+
/--
490+
error: Invalid field `s`: The environment does not contain `S.s`
491+
s
492+
has type
493+
S
494+
495+
Note: A private declaration `S.s` (from the current module) exists but would need to be public to access here.
496+
-/
497+
#guard_msgs in
498+
@[expose] public def useS (s : S) := s.s

0 commit comments

Comments
 (0)