You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lean4game/server/testgame/TestGame/StructInstWithHoles.lean

1699 lines
80 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/- Copied from `https://github.com/leanprover-community/mathlib4/tree/thorimur/refine_struct-via-StructInst` -/
/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.Util.FindExpr
import Lean.Parser.Term
import Lean.Meta.Structure
import Lean.Elab.App
import Lean.Elab.Binders
set_option autoImplicit true
/-!
# Structure Instances With (Variadic) Holes
This file defines a term elaborator for structure instance syntax that includes "variadic holes",
i.e. holes of "variable length", which are represented via via the syntax `?..`, `?..!`, etc.,
e.g. `{ x := 0, ?.. }`.
This serves to port the functionality of mathlib3's `refine_struct { .. }`, but via `refine`
(e.g. `refine { ?.. }`).
Lean currently already supports one form of variadic hole in structure instances, namely `..`,
which fills all unspecified fields with natural metavariables and which is frequently used in
pattern matching.
Variadic holes that begin with a question mark are meant to parallel named holes (e.g. `?x`).
Therefore, syntax like `{ x := 0, ?.. }` will fill all remaining fields with metavariables named
by the field in question (e.g. `y := ?y`). Identifiers can optionally be provided to prefix the
name; see below.
Tests are performed in `Tests/StructInstWithHoles.lean`.
At the end of this file, we implement `haveFieldProj` in analogy to `have_field` with some
modifications. (This might be moved or eliminated.)
## Current specifics
The following is subject to change.
Several variants are supported to enable the choice between synthetic holes (`?`) and natural
ones, and to enable the choice between filling all unspecified fields with holes (`!`) and
synthesizing defaults where possible.
Currently only three combinations are allowed, since I can't see a use for unnamed/natural goals
with synthesized defaults. More descriptive syntax might be used for indicating whether to
synthesize defaults or not (e.g. `?.. noDefaults`)
synthesize defaults
┌──true──┬─false──┐
named goals │ `?..` │ `?..!` │
├────────┼────────┤
unnamed goals │ × │ `..` │
└────────┴────────┘
Identifiers can be provided after the variadic hole syntax in the `?` case, e.g. `?..foo` and
`?..!foo`. These will be prefixed to the goal name. For example, a field `y`'s goal will be named
`foo.y` instead of `y`.
# Overview of existing code
This document began as `StructInst.lean`, then was modified as needed. The modifications are done
in such a way as to (hopefully) make it easy for them to be absorbed into `StructInst.lean`, if
that's where it ends up. The following details how the original `StructInst.lean` works (which is
largely preserved).
## Short version
The way this works is that we start with syntax, parse it into a bare-bones `Struct`, use
`expandStruct` to expand that struct into another struct that has intermediate indicators
(`FieldVal`s) holding raw syntax or accounting for its absence. We then `elabStruct` that struct
into an `ElabStructResult` which has the potential result expression (an application of the
structure's single constructor to its values, which may be metavariables if they weren't found in
the syntax) plus info on the original struct. We then synthesize defaults using `propagate` to
assign any metavariables standing in for default values, and then return the expression.
## Long version
We start with turning the syntax into a struct. First we extract the sources (everything before
the `with` and any variadic holes (`..` or similar)), then we feed this to `elabStructInstAux`
along with the raw syntax and expected type. Inside `elabStructInstAux` we make the syntax into a
Struct (`mkStructView`), then `expandStruct`.
There's a "pre-expression scaffolding/framework/spine" set up early on in the process in the form
of FieldVal's, which hold raw information: `.term stx` where `stx` is syntax, if a term was
provided; a `.missing` value if it was missing; or a `.nested s` value where `s` is a `Struct` if
a subobject relation obtains. The `FieldVal`s for a field might be modified as elaboration
proceeds: for example, some might become `.nested`, or some defaults might turn into terms. This
all happens during `expandStruct`.
The `Struct` holds everything, and is updated throughout the process.
One of its fields is `field`, which holds a list of `Field Struct`'s. (The appearance of `Struct`
within `Field Struct` is to allow the `Struct`s to nest other `Struct`s when we have subobjects.)
The fields of each element of the `field` field (got that?) are
* `ref : Syntax`, which holds the `Syntax` found for that field
* `lhs : FieldLHS`, which describes the name of the field in question
* `val : FieldVal`, which holds the pre-expression `FieldVal`—either `.term stx` where `stx` is
the syntax of the field's value, `.default` (now `.missing`) if no syntax was found, or `.nested
s` if the field represents a subobject `s` of the structure (e.g. `toFoo`, produced by `extends`)
* `expr? : Option Expr`, which holds the elaborated expression when it becomes available (or a
metavariable, if the syntax is missing), and which begins at this stage as `none`.
`elacStructInstAux` then calls `elabStruct` on the skeletal `Struct` (which has appropriate
`FieldVal`s, but `none` for each field's `expr?`), which turns the `Struct` into an
`ElabStructResult`.
`elabStruct` elaborates everything but defaults, constructing the structure instance as an
expression given by the application of the structure's constructor to the values it finds by
elaborating the `stx` in any `.term stx` `FieldVal` while ensuring the appropriate type. (It's
not quite true that no defaults are taken care of here: `autoparam`s are turned into `.term`s.)
If the `FieldVal` is `.nested s`, it calls `elabStruct` on `s`; if it finds a `.default` (now
renamed to `.missing`) `FieldVal`, it uses a fresh metavariable in place of an elaborated
expression. As it does this, it stores any elaborated expressions in the `expr?` field of its
fields and builds this constructor application expression, which is, in the resulting
`ElabStructResult` structure, confusingly also named "val". Occasionally the `FieldVals` for each
field are updated as well. Also returned in `ElabStructResult` is the updated Struct with all its
new fields, and `instMVars`, an array of metavariables for dealing with typeclass instance
synthesis.
During `elabStruct`, defaults were inserted as metavariables into the constructed expression and
into `expr?`, but they were also annotated with ``structInstDefault` to indicate that they
represented a missing default value, and needed to be synthesized during the default loop.
Indeed, the function `isMissingDefault?` checks that this metavariable is unassigned when
deciding whether to return true or false. We finish our elaboration of the structure instance
with the `propagate` loop, which iteratively synthesizes the defaults, as sometimes the default
values of fields reference other fields which may also have a default value (etc.).
# Modifications
Changes from `StructInst.lean` are no longer marked with `!!` in a comment (or with `!!/`, `!!\`
surrounding a new or altered block). These are however visible in past commits.
Existing comments are left unchanged, and new comments begin with "~~".
## Syntax
We use the parser from `Term.lean`, but change `optional ".."` to our parseer for variadic holes,
`variadicHole`
## Logic
The original implementation of `..`, which creates a natural metavariable for each goal (and does
not synthesize defaults) affects things early on, at the stage of `FieldVal`s. Instead of using a
`.default` `FieldVal`, it makes a hole via syntax by providing a `.term (mkHole ref)` `FieldVal`
for each missing field value.
We preserve this behavior only for the `isSynthetic := false, useDefaults := false` case.
Otherwise, we use the `.default` `FieldVal`—now renamed to `.missing` to reflect its changed
function—and intervene mostly within the `ImplicitFields` namespace (previously the
`DefaultFields` namespace), where defaults are synthesized. If the variadic hole syntax says not
to, we don't propagate the default-synthesizing loop and therefore don't synthesize any defaults.
Typically, when the default loop ends with some fields still annotated as missing, an error is
thrown (`fields missing: ...`). However, if there's a variadic hole, we simply return from the
loop without error; next (whether the loop has run or not) we assign those remaining annotated
fields to *new* metavariables, which, in the `isSynthetic := true` case, are well-named and hold
all of the metadata we want them to. These are ripe to be used in a `refine` statement.
The only exception to this flow is how we handle `autoparam`s: autoparams are handled in
`elabStruct`, so that's where we intervene as well.
## Style
New code is often written in a "lookahead" fashion, to make it as easy as possible to move this
to core, in case it would better belong there. Therefore some cases that don't occur in this
elaboration are nonetheless accounted for—for example, the case where variadic hole syntax is
absent (where the value of struct.source.implicit is none), and the case where `isSynthetic :=
false, useDefaults := false`, which is already accounted for by existing `..` syntax. We use a
different token (`...`) only to show that this works.
This modification of `StructInst.lean` also attempts to be "minimally invasive" by intervening in
as few places as possible and leaving the existing flow of computation intact.
## Locations of changes
The changes to existing definitions are localized to the following:
### Necessary parsing and syntax processing changes
* The `structInst` term parser was modified to allow variadic hole syntax.
* `expandStructInstFieldAbbrev`
* update `$[..%$ell]?` syntax match to accommodate variadic holes
* type of `implicit` in `Source` structure: `Option Syntax` ⇒ `Option VariadicHoleConfig`
* originally this held the syntax `..` if present; now it holds information derived from the
variadic hole syntax (if present) as opposed to the raw syntax
* `getStructSource`
* inserted `getVariadicHoleConfig?` in front of the raw `implicitSource` syntax to process it
into a `VariadicHoleConfig`
* `formatStruct`
* instead of using a literal `".."` whenever `implicit.isSome`, use the syntax we encountered
(stored as one of the fields of `VariadicHoleConfig`)
### Logic
**Simple renamings**
* The `.default` `FieldVal`, which was used to indicate that fields would be synthesized by the
default loop, is renamed to `.missing` for clarity. Likewise `Struct.allDefault` is renamed to
`Struct.allMissing` (since it simply checks these `FieldVal`s), and `formatField` formats
`.missing` fields via `"<missing>"` instead of via `"<default>"`.
* We rename the `DefaultFields` namespace to `ImplicitFields`, since this is now where we
intervene to handle holes as well.
**Setup changes**
* `addMissingFields`, which is responsible for attaching `.missing` to unspecified fields,
previously attached a `.term (mkHole stx)` to any missing field whenever `..` was present. Here,
we only do so in the `isSynthetic := false, useDefaults := false` case, attaching `.missing` in
all other cases (both when we exepct them to be synthesized as defaults and not).
* `elabStruct` this function uses `FieldVal`s to 1) generate `expr?`s for each field when
possible and 2) apply the structure's constructor to the arguments it finds to build the instance
expression. It stops short of synthesizing defaults, inserting a metavariable in both places when
it encounters a `.missing` field. However, autoparams for `.missing` values are handled here. We
therefore modify that section of the code so that
* if the variadic hole says to use holes instead of defaults, we don't try the autoparam
* if it says to use defaults, we try the autoparam in such a way that if it fails we use a hole
instead
* we also need to introduce a new optional `Bool` argument to the internal `cont` function
that, when modified from its default, takes a different branch. Otherwise `cont` is
untouched, and the original behavior is used when this argument is not specified.
* if there's no variadic hole, use the original behavior
**Default loop changes**
* `propagateLoop` this is a pass of the loop used for synthesizing defaults, and is responsible
for throwing an error when too few fields are specified. If there is a variadic hole, it does not
throw that error and simply returns.
* `propagate` this sets things up for the loop and executes it.
* We check the variadic hole config and don't run `propagateLoop` if it says not to. (We start
with a `do` to accommodate this.)
* At the end, if there is a variadic hole, we run `assignRemainingDefaultsToFieldHoles` (a new
function which does what you expect)
## New Code
The new functionality is in the `ImplicitFields` section to attach metadata to the goals
produced. Currently, we attach the metadata as a `KVMap` to the type of the goal, but this may
change. We use this metadata to resolve name conflicts, appending an appropriate index if any
existing metavariable is from a structure that shares a field name. This is meant to improve
clarity: for example, if `Foo` and `Bar` both have fields `x` and `y`,
`refine ({ y := 0, ?.. : Foo}, { x := 1, ?.. : Bar})` will produce goals `x` and `y_1` to show
that these are not from the same structure. (This may change if we decide to prefix each goal
name with the name of the structure.)
# Questions
* Should `trySynthStructInstance?` be run even when `useDefaults` is `false`?
* what about `bi == .instImplicit`? Should default synthesis be avoided in that case too?
* Dhould metadata be on the type, or somewhere else?
* Is the best way to get a unique id for a syntax instance via getPos? Do we need to do so anyway?
* Should utility-like functions be refactored into other files?
* Can the docstrings of the original structure instance and `refine` be modified from "within
mathlib" somehow?
* Do I need to add to "authors" at the top or worry about the copyright?
* Check for unreachable code after design decisions have been made.
-/
namespace Lean.Elab.Term.StructInstWithHoles
open Meta
open TSyntax.Compat
/--
A synthetic variadic hole. When used in structure instance syntax, it fills the unspecified
fields with metavariables named by the field. It synthesizes defaults when possible.
It can be followed by an identifier to specify a prefix for the names of the holes.
-/
def vH?dd := leading_parser "?.." >> Parser.optional Parser.ident
/--
A synthetic variadic hole that does not synthesize any defaults. When used in structure instance
syntax, it fills the unspecified fields with metavariables named by the field. It synthesizes
defaults when possible. It can be followed by an identifier to specify a prefix for the names of
the holes.
-/
def vH?dd! := leading_parser "?..!" >> Parser.optional Parser.ident
/-~~! Removed for now.
/-- A natural variadic hole that does not synthesize defaults. -/
def vHdd! := leading_parser "..!"
/-- A natural variadic hole that synthesizes defaults when possible. -/
def vHdd := leading_parser "..."
-/
/-- A variadic hole that fills multiple spots with holes. -/
def variadicHole := leading_parser (vH?dd <|> vH?dd! /-~~! <|> vHdd! <|> vHdd-/)
open Lean.Parser Lean.Parser.Term in
/--
Structure instance. `{ x := e, ... }` assigns `e` to field `x`, which may be
inherited. If `e` is itself a variable called `x`, it can be elided:
`fun x => { x , y := 1 }`.
A *structure update* of an existing value can be given via `with`:
`{ point with x := 1 }`.
The structure type can be specified if not inferable:
`{ x := 1, y := 2 : Point }`.
`..` can be used in pattern-matching to fill all unspecified fields with `_`:
`match s with | { x := 1, .. } => ...`
`?..` fills all unspecified fields with automatically-named goals:
if a `Foo` has fields `x`, `y`, `z`, `{ x := 1, ?.. } : Foo` creates `?y`, `?z`.
-/
@[term_parser] def structInstWithHoles := leading_parser "{" >> ppHardSpace >> Lean.Parser.optional
(atomic (sepBy1 termParser ", " >> " with "))
>> sepByIndent (structInstFieldAbbrev <|> structInstField) ", " (allowTrailingSep := true)
>> variadicHole -- Only apply this elaboration to syntax that has one of these holes
>> Lean.Parser.optional (" : " >> termParser) >> " }"
/--
Syntactically move any type specification outside of the structure instance syntax:
`{ x := 0 : Foo }` becomes `{ x := 0 } : Foo`.
-/
@[macro structInstWithHoles] def expandStructInstWithHolesExpectedType : Macro := fun stx =>
let expectedArg := stx[4]
if expectedArg.isNone then
Macro.throwUnsupported
else
let expected := expectedArg[1]
let stxNew := stx.setArg 4 mkNullNode
`(($stxNew : $expected))
/-- Expand field abbreviations. Example: `{ x, y := 0 }` expands to `{ x := x, y := 0 }` -/
@[macro structInstWithHoles] def expandStructInstWithHolesFieldAbbrev : Macro
| `({ $[$srcs,* with]? $fields,* $ell:variadicHole $[: $ty]? }) =>
if fields.getElems.raw.any (·.getKind == ``Lean.Parser.Term.structInstFieldAbbrev) then do
let fieldsNew ← fields.getElems.mapM fun
| `(Parser.Term.structInstFieldAbbrev| $id:ident) =>
`(Parser.Term.structInstField| $id:ident := $id:ident)
| field => return field
`({ $[$srcs,* with]? $fieldsNew,* $ell $[: $ty]? })
else
Macro.throwUnsupported
| _ => Macro.throwUnsupported
/--
If `stx` is of the form `{ s₁, ..., sₙ with ... }` and `sᵢ` is not a local variable, expand into
`let src := sᵢ; { ..., src, ... with ... }`.
Note that this one is not a `Macro` because we need to access the local context.
-/
private def expandNonAtomicExplicitSources (stx : Syntax) : TermElabM (Option Syntax) := do
let sourcesOpt := stx[1]
if sourcesOpt.isNone then
return none
else
let sources := sourcesOpt[0]
if sources.isMissing then
throwAbortTerm
let sources := sources.getSepArgs
if (← sources.allM fun source => return (← isLocalIdent? source).isSome) then
return none
if sources.any (·.isMissing) then
throwAbortTerm
return some (← go sources.toList #[])
where
go (sources : List Syntax) (sourcesNew : Array Syntax) : TermElabM Syntax := do
match sources with
| [] =>
let sources := Syntax.mkSep sourcesNew (mkAtomFrom stx ", ")
return stx.setArg 1 (stx[1].setArg 0 sources)
| source :: sources =>
if (← isLocalIdent? source).isSome then
go sources (sourcesNew.push source)
else
withFreshMacroScope do
let sourceNew ← `(src)
let r ← go sources (sourcesNew.push sourceNew)
`(let src := $source; $r)
/-- Information for any explicit sources encountered (i.e. some `sᵢ` in `s₁, ..., sₙ with`) -/
structure ExplicitSourceInfo where
/-- The syntax of some `sᵢ` in `s₁, ..., sₙ with` -/
stx : Syntax
/-- The name of some structure `sᵢ` in `s₁, ..., sₙ with` -/
structName : Name
deriving Inhabited
/--
Information deduced from variadic hole syntax, as well as the raw
syntax itself and any identifiers that were found.
-/
structure VariadicHoleConfig where
/-- The raw variadic hole syntax encountered (`?..`, `..`, etc.)-/
stx : TSyntax ``variadicHole
/-- An optional name `x` found in `?..x` or `?..!x`, to be used as a prefix. -/
name : Option Name := none
/-- Whether the holes should be synthetic and automatically named. -/
isSynthetic : Bool
/-- Whether defaults should attempt to be synthesized before filling fields with holes. -/
useDefaults : Bool
deriving Inhabited, Repr
/--
Information on other sources of field values via structure update syntax or variadic holes.
Collects of explicit source info (preceding `with` in structure updates) and implicit source
info (for specification of holes, e.g. `..` or `?..`).
-/
structure Source where
/-- info for all `sᵢ` in `s₁, ..., sₙ with` -/
explicit : Array ExplicitSourceInfo
/-- info for any variadic hole syntax encountered (`?..`, `..`, etc.) -/
implicit : Option VariadicHoleConfig
deriving Inhabited
/-- Check if neither an explicit nor an implicit source has been specified. -/
def Source.isNone : Source → Bool
| { explicit := #[], implicit := none } => true
| _ => false
/-- Process variadic hole syntax into a VariadicHoleConfig. -/
def getVariadicHoleConfig? : TSyntax ``variadicHole → Option VariadicHoleConfig
| stx => match stx with
| `(variadicHole|?..$[$x:ident]?) => some
{stx, isSynthetic := true, useDefaults := true, name := x.map Syntax.getId}
| `(variadicHole|?..!$[$x:ident]?) => some
{stx, isSynthetic := true, useDefaults := false, name := x.map Syntax.getId}
| _ => none
/--
Put an array of source syntax into a form which matches
`optional (atomic (sepBy1 termParser ", " >> " with ")`, e.g. `s₁, s₂, s₃ with`.
Should only be called when `sources` is a nonempty `Array`.
-/
private def mkSourcesWithSyntax (sources : Array Syntax) : Syntax :=
let ref := sources[0]!
let stx := Syntax.mkSep sources (mkAtomFrom ref ", ")
mkNullNode #[stx, mkAtomFrom ref "with "]
/--
Extract and process both explicit (`s₁, ..., sₙ with`) and implicit (`..`, `?..`, etc.) source
syntax from structure syntax.
-/
private def getStructSource (structStx : Syntax) : TermElabM Source :=
withRef structStx do
let explicitSource := structStx[1]
let implicitSource := structStx[3]
let explicit ← if explicitSource.isNone then
pure #[]
else
explicitSource[0].getSepArgs.mapM fun stx => do
let some src ← isLocalIdent? stx | unreachable!
addTermInfo' stx src
let srcType ← whnf (← inferType src)
tryPostponeIfMVar srcType
let structName ← getStructureName srcType
return { stx, structName }
let implicit :=
if implicitSource[0].isNone
then none
else getVariadicHoleConfig? implicitSource
return { explicit, implicit }
/--
We say a `{ ... }` notation is a `modifyOp` if it contains only one
```
def structInstArrayRef := leading_parser "[" >> termParser >>"]"
```
-/
private def isModifyOp? (stx : Syntax) : TermElabM (Option Syntax) := do
let s? ← stx[2].getSepArgs.foldlM (init := none) fun s? arg => do
/- arg is of the form `structInstFieldAbbrev <|> structInstField` -/
if arg.getKind == ``Lean.Parser.Term.structInstField then
/- Remark: the syntax for `structInstField` is
```
def structInstLVal := leading_parser (ident <|> numLit <|> structInstArrayRef) >> many
(group ("." >> (ident <|> numLit)) <|> structInstArrayRef)
def structInstField := leading_parser structInstLVal >> " := " >> termParser
```
-/
let lval := arg[0]
let k := lval[0].getKind
if k == ``Lean.Parser.Term.structInstArrayRef then
match s? with
| none => return some arg
| some s =>
if s.getKind == ``Lean.Parser.Term.structInstArrayRef then
throwErrorAt arg "invalid \{...} notation, at most one `[..]` at a given level"
else
throwErrorAt arg "invalid \{...} notation, can't mix field and `[..]` at a given level"
else
match s? with
| none => return some arg
| some s =>
if s.getKind == ``Lean.Parser.Term.structInstArrayRef then
throwErrorAt arg "invalid \{...} notation, can't mix field and `[..]` at a given level"
else
return s?
else
return s?
match s? with
| none => return none
| some s => if s[0][0].getKind == ``Lean.Parser.Term.structInstArrayRef then return s? else
return none
/-- Elaborate `modifyOp`s given a single explicit source. -/
private def elabModifyOp (stx modifyOp : Syntax) (sources : Array ExplicitSourceInfo)
(expectedType? : Option Expr) : TermElabM Expr := do
if sources.size > 1 then
throwError "invalid \{...} notation, multiple sources and array update is not supported."
let cont (val : Syntax) : TermElabM Expr := do
let lval := modifyOp[0][0]
let idx := lval[1]
let self := sources[0]!.stx
let stxNew ← `($(self).modifyOp (idx := $idx) (fun s => $val))
trace[Elab.struct.modifyOp] "{stx}\n===>\n{stxNew}"
withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
let rest := modifyOp[0][1]
if rest.isNone then
cont modifyOp[2]
else
let s ← `(s)
let valFirst := rest[0]
let valFirst := if valFirst.getKind == ``Lean.Parser.Term.structInstArrayRef then valFirst
else valFirst[1]
let restArgs := rest.getArgs
let valRest := mkNullNode restArgs[1:restArgs.size]
let valField := modifyOp.setArg 0 <| mkNode ``Parser.Term.structInstLVal #[valFirst, valRest]
let valSource := mkSourcesWithSyntax #[s]
let val := stx.setArg 1 valSource
let val := val.setArg 2 <| mkNullNode #[valField]
trace[Elab.struct.modifyOp] "{stx}\nval: {val}"
cont val
/--
Get structure name.
This method tries to postpone execution if the expected type is not available.
If the expected type is available and it is a structure, then we use it.
Otherwise, we use the type of the first source.
-/
private def getStructName (expectedType? : Option Expr) (sourceView : Source) : TermElabM Name := do
tryPostponeIfNoneOrMVar expectedType?
let useSource : Unit → TermElabM Name := fun _ => do
unless sourceView.explicit.isEmpty do
return sourceView.explicit[0]!.structName
match expectedType? with
| some expectedType => throwUnexpectedExpectedType expectedType
| none => throwUnknownExpectedType
match expectedType? with
| none => useSource ()
| some expectedType =>
let expectedType ← whnf expectedType
match expectedType.getAppFn with
| Expr.const constName _ =>
unless isStructure (← getEnv) constName do
throwError "invalid \{...} notation, structure type expected{indentExpr expectedType}"
return constName
| _ => useSource ()
where
throwUnknownExpectedType :=
throwError "invalid \{...} notation, expected type is not known"
throwUnexpectedExpectedType type (kind := "expected") := do
let type ← instantiateMVars type
if type.getAppFn.isMVar then
throwUnknownExpectedType
else
throwError "invalid \{...} notation, {kind} type is not of the form (C ...){indentExpr type}"
/-- Information on the left hand side of a binding encountered in structure syntax. -/
inductive FieldLHS where
/-- A representation of the name of a field as encountered in binding syntax (e.g. `x` in
`x := ...`). -/
| fieldName (ref : Syntax) (name : Name)
/-- A representation of the index of a field as encountered in binding syntax (e.g. `3` in
`3 := ...`). -/
| fieldIndex (ref : Syntax) (idx : Nat)
/-- A representation of a modifyOp as encountered in binding syntax. -/
| modifyOp (ref : Syntax) (index : Syntax)
deriving Inhabited
instance : ToFormat FieldLHS := ⟨fun lhs =>
match lhs with
| .fieldName _ n => format n
| .fieldIndex _ i => format i
| .modifyOp _ i => "[" ++ i.prettyPrint ++ "]"⟩
/--
A limited, pre-expression description of the values of fields. Only terms given by raw syntax,
nested values (for subobjects), and missing values are can be specified.
The polymorphism via its `Type` argument is only used for nested `FieldVal`s, which need to
know what type their argument should be. In practice, we only ever take this argument to be
`Struct`.
-/
inductive FieldVal (σ : Type) where
/-- Term syntax encountered on the RHS of a binding, e.g. `1+1` in `x := 1+1`. -/
| term (stx : Syntax) : FieldVal σ
/-- A nested `FieldVal`, which in practice is used to hold subobjects as `Struct`s. -/
| nested (s : σ) : FieldVal σ
/-- An indication that this field was missing, i.e. not specified explicitly in the syntax. -/
| missing : FieldVal σ
deriving Inhabited
/--
A representation of a field in a structure. This contains the original syntax of the field
(`ref`), a representation of the LHS of the `:=` binding (`lhs`), the pre-expression `FieldVal`
(`val`), and the actual expression that we take to be the value of the field (`expr?`).
`expr?` begins as `none`, and is modified over the course of this code as we figure out whether
we need to elaborate some syntax encountered (e.g. if `.term stx` is in `val`) or if the field
value is `.missing` (in which case we make a metavariable).
-/
structure Field (σ : Type) where
/-- The syntax of the binding used to specify this field. -/
ref : Syntax
/-- Information on the LHS of the binding used to specify this field. -/
lhs : List FieldLHS
/-- The basic content of the field value, prior to elaboration. -/
val : FieldVal σ
/-- The elaborated value of the field in question as it becomes available, which starts
out as `none` and is updated during `elabStruct` with either elaborated terms or
metavariables which may get assigned to synthesized defaults. -/
expr? : Option Expr := none
deriving Inhabited
/-- Check if the LHS of the binding specifying a field is a single `FieldLHS`. -/
def Field.isSimple {σ} : Field σ → Bool
| { lhs := [_], .. } => true
| _ => false
/--
The organized content of the structure instance.
The field `params` is used for `.missing` value propagation. It is initially empty, and
then set at `elabStruct`. -/
inductive Struct where
| mk (ref : Syntax) (structName : Name) (params : Array (Name × Expr))
(fields : List (Field Struct)) (source : Source)
deriving Inhabited
/-- Abbreviation for `List (Field Struct)`: A list of representations of the structure's fields. -/
abbrev Fields := List (Field Struct)
/-- The original syntax of the structure instance. -/
def Struct.ref : Struct → Syntax
| ⟨ref, _, _, _, _⟩ => ref
/-- The name of the structure. -/
def Struct.structName : Struct → Name
| ⟨_, structName, _, _, _⟩ => structName
/-- Parameters used during the initial processing of `.missing` fields. Initially `none`, and set
at `elabStruct`. -/
def Struct.params : Struct → Array (Name × Expr)
| ⟨_, _, params, _, _⟩ => params
/-- The list of `fields` in the structure instance as `Field Struct`s. Updated over the course of
the elaboration to include computed values. -/
def Struct.fields : Struct → Fields
| ⟨_, _, _, fields, _⟩ => fields
/-- Information on other sources of values for the structure. Namely, any structures preceding
`with` in structure update syntax and any variadic holes (`..`, `?..`) following the field
bindings. -/
def Struct.source : Struct → Source
| ⟨_, _, _, _, s⟩ => s
/-- `true` iff all fields of the given structure are marked as `.missing`. -/
partial def Struct.allMissing (s : Struct) : Bool :=
s.fields.all fun { val := val, .. } => match val with
| .term _ => false
| .missing => true
| .nested s => allMissing s
/-- Pretty-prints a field (`Field Struct`). Uses the field LHS's and its `val : FieldVal Struct`. -/
def formatField (formatStruct : Struct → Format) (field : Field Struct) : Format :=
Format.joinSep field.lhs " . " ++ " := " ++
match field.val with
| .term v => v.prettyPrint
| .nested s => formatStruct s
| .missing => "<missing>"
/-- Pretty-prints a `Struct`. -/
partial def formatStruct : Struct → Format
| ⟨_, _, _, fields, source⟩ =>
let fieldsFmt := Format.joinSep (fields.map (formatField formatStruct)) ", "
let implicitFmt := match source.implicit with
| some v => format v.stx
| none => ""
if source.explicit.isEmpty then
"{" ++ fieldsFmt ++ implicitFmt ++ "}"
else
"{" ++ format (source.explicit.map (·.stx)) ++ " with " ++ fieldsFmt ++ implicitFmt ++ "}"
instance : ToFormat Struct := ⟨formatStruct⟩
instance : ToString Struct := ⟨toString ∘ format⟩
instance : ToFormat (Field Struct) := ⟨formatField formatStruct⟩
instance : ToString (Field Struct) := ⟨toString ∘ format⟩
/--
Turns a `FieldLHS` into syntax. The first argument specifies whether this is the first in a list of
`FieldLHS`'s or not.
Recall that `structInstField` elements have the form
```
def structInstField := leading_parser structInstLVal >> " := " >> termParser
def structInstLVal := leading_parser (ident <|> numLit <|> structInstArrayRef)
>> many (("." >> (ident <|> numLit)) <|> structInstArrayRef)
def structInstArrayRef := leading_parser "[" >> termParser >>"]"
```
Remark: this code relies on the fact that `expandStruct` only transforms `fieldLHS.fieldName`
-/
def FieldLHS.toSyntax (first : Bool) : FieldLHS → Syntax
| .modifyOp stx _ => stx
| .fieldName stx name => if first then mkIdentFrom stx name
else mkGroupNode #[mkAtomFrom stx ".", mkIdentFrom stx name]
| .fieldIndex stx _ => if first then stx else mkGroupNode #[mkAtomFrom stx ".", stx]
/-- Extracts the `stx` from a `.term stx : FieldVal Struct`. Panics when called on any other
constructor of `FieldVal Struct`. -/
def FieldVal.toSyntax : FieldVal Struct → Syntax
| .term stx => stx
| _ => unreachable!
/-- Turns a field (as a `Field Struct`) into syntax if has a `val` of the form `.term stx`; panics
otherwise. Panics if the `lhs` is an empty list. -/
def Field.toSyntax : Field Struct → Syntax
| field =>
let stx := field.ref
let stx := stx.setArg 2 field.val.toSyntax
match field.lhs with
| first::rest => stx.setArg 0 <| mkNullNode
#[first.toSyntax true, mkNullNode <| rest.toArray.map (FieldLHS.toSyntax false) ]
| _ => unreachable!
/-- Processes syntax into a `FieldLHS`. -/
private def toFieldLHS (stx : Syntax) : MacroM FieldLHS :=
if stx.getKind == ``Lean.Parser.Term.structInstArrayRef then
return FieldLHS.modifyOp stx stx[1]
else
-- Note that the representation of the first field is different.
let stx := if stx.getKind == groupKind then stx[1] else stx
if stx.isIdent then
return FieldLHS.fieldName stx stx.getId.eraseMacroScopes
else match stx.isFieldIdx? with
| some idx => return FieldLHS.fieldIndex stx idx
| none => Macro.throwError "unexpected structure syntax"
/-- Processes structure instance syntax into a `Struct` given the `structName` and its `source`s. -/
private def mkStructView (stx : Syntax) (structName : Name) (source : Source) : MacroM Struct := do
/- Recall that `stx` is of the form
```
leading_parser "{" >> optional (atomic (sepBy1 termParser ", " >> " with "))
>> sepByIndent (structInstFieldAbbrev <|> structInstField) ...
>> variadicHole
>> optional (" : " >> termParser)
>> " }"
```
This method assumes that `structInstFieldAbbrev` had already been expanded.
-/
let fields ← stx[2].getSepArgs.toList.mapM fun fieldStx => do
let val := fieldStx[2]
let first ← toFieldLHS fieldStx[0][0]
let rest ← fieldStx[0][1].getArgs.toList.mapM toFieldLHS
return { ref := fieldStx, lhs := first :: rest, val := FieldVal.term val : Field Struct }
return ⟨stx, structName, #[], fields, source⟩
/-- (Monadic) Modifies a `Struct`'s fields with a monadic function. -/
def Struct.modifyFieldsM {m : Type → Type} [Monad m] (s : Struct) (f : Fields → m Fields) :
m Struct :=
match s with
| ⟨ref, structName, params, fields, source⟩ =>
return ⟨ref, structName, params, (← f fields), source⟩
/-- Modify a `Struct`'s `Fields` with a function. -/
def Struct.modifyFields (s : Struct) (f : Fields → Fields) : Struct :=
Id.run <| s.modifyFieldsM f
/-- Overwrite a `Struct`'s fields. -/
def Struct.setFields (s : Struct) (fields : Fields) : Struct :=
s.modifyFields fun _ => fields
/-- Overwrite a `Struct`'s params. -/
def Struct.setParams (s : Struct) (ps : Array (Name × Expr)) : Struct :=
match s with
| ⟨ref, structName, _, fields, source⟩ => ⟨ref, structName, ps, fields, source⟩
/-- Breaks down non-anonymous names in the lhs of fields into lists of their components. -/
private def expandCompositeFields (s : Struct) : Struct :=
s.modifyFields fun fields => fields.map fun field => match field with
| { lhs := .fieldName _ (.str Name.anonymous ..) :: _, .. } => field
| { lhs := .fieldName ref n@(.str ..) :: rest, .. } =>
let newEntries := n.components.map <| FieldLHS.fieldName ref
{ field with lhs := newEntries ++ rest }
| _ => field
/-- Replaces field lhs's that are specified by index with the name of the field (as registered in
the structure). -/
private def expandNumLitFields (s : Struct) : TermElabM Struct :=
s.modifyFieldsM fun fields => do
let env ← getEnv
let fieldNames := getStructureFields env s.structName
fields.mapM fun field => match field with
| { lhs := .fieldIndex ref idx :: rest, .. } =>
if idx == 0 then throwErrorAt ref "invalid field index, index must be greater than 0"
else if idx > fieldNames.size
then throwErrorAt ref "invalid field index, structure has only #{fieldNames.size} fields"
else return { field with lhs := .fieldName ref fieldNames[idx - 1]! :: rest }
| _ => return field
/-- For example, consider the following structures:
```
structure A where
x : Nat
structure B extends A where
y : Nat
structure C extends B where
z : Bool
```
This method expands parent structure fields using the path to the parent structure.
For example,
```
{ x := 0, y := 0, z := true : C }
```
is expanded into
```
{ toB.toA.x := 0, toB.y := 0, z := true : C }
```
-/
private def expandParentFields (s : Struct) : TermElabM Struct := do
let env ← getEnv
s.modifyFieldsM fun fields => fields.mapM fun field => do match field with
| { lhs := .fieldName ref fieldName :: _, .. } =>
addCompletionInfo <| CompletionInfo.fieldId ref fieldName (← getLCtx) s.structName
match findField? env s.structName fieldName with
| none => throwErrorAt ref "'{fieldName}' is not a field of structure '{s.structName}'"
| some baseStructName =>
if baseStructName == s.structName then pure field
else match getPathToBaseStructure? env baseStructName s.structName with
| some path =>
let path := path.map fun funName => match funName with
| .str _ s => .fieldName ref (Name.mkSimple s)
| _ => unreachable!
return { field with lhs := path ++ field.lhs }
| _ => throwErrorAt ref "failed to access field '{fieldName}' in parent structure"
| _ => return field
/-- Abbreviation for `HashMap Name Fields`: A hash map from field names to lists of representations
of fields. -/
private abbrev FieldMap := HashMap Name Fields
/-- Creates a hash map from field names to lists of representations of fields. The length of the
list can be greater than one if the field is not simple. Panics if the lhs of a field is empty.
-/
private def mkFieldMap (fields : Fields) : TermElabM FieldMap :=
fields.foldlM (init := {}) fun fieldMap field =>
match field.lhs with
| .fieldName _ fieldName :: _ =>
match fieldMap.find? fieldName with
| some (prevField::restFields) =>
if field.isSimple || prevField.isSimple then
throwErrorAt field.ref "field '{fieldName}' has already been specified"
else
return fieldMap.insert fieldName (field::prevField::restFields)
| _ => return fieldMap.insert fieldName [field]
| _ => unreachable!
/-- Unwraps a `Field Struct` from a list of length one, and otherwise returns `none`. -/
private def isSimpleField? : Fields → Option (Field Struct)
| [field] => if field.isSimple then some field else none
| _ => none
/-- Finds the index of the field name in its third argument in the list of field names in its
second. The first argument, the name of the structure, is used only for descriptive error
messages. -/
private def getFieldIdx (structName : Name) (fieldNames : Array Name) (fieldName : Name) :
TermElabM Nat := do
match fieldNames.findIdx? fun n => n == fieldName with
| some idx => return idx
| none => throwError "field '{fieldName}' is not a valid field of '{structName}'"
/-- Constructs the syntax for a field projection. Only does so if the given field name is in fact a
field of the given structure name; returns `none` otherwise. -/
def mkProjStx? (s : Syntax) (structName : Name) (fieldName : Name) : TermElabM (Option Syntax) := do
if (findField? (← getEnv) structName fieldName).isNone then
return none
return some <| mkNode ``Parser.Term.proj #[s, mkAtomFrom s ".", mkIdentFrom s fieldName]
/-- Gets a field from a list of fields by name. If not found, returns `none`. -/
def findField? (fields : Fields) (fieldName : Name) : Option (Field Struct) :=
fields.find? fun field =>
match field.lhs with
| [.fieldName _ n] => n == fieldName
| _ => false
mutual
/-- Group fields that belong to a subobject as a `Struct` under that subobject field via a
`.nested s` `FieldVal`. For example, a `Struct` representing
`{ toFoo.x := 1, toFoo.y := 2, z := 3 }` will become one representing
`{ toFoo := { x := 1, y := 2 }, z := 3 }`.
-/
private partial def groupFields (s : Struct) : TermElabM Struct := do
let env ← getEnv
withRef s.ref do
s.modifyFieldsM fun fields => do
let fieldMap ← mkFieldMap fields
fieldMap.toList.mapM fun ⟨fieldName, fields⟩ => do
match isSimpleField? fields with
| some field => pure field
| none =>
let substructFields := fields.map fun field => { field with lhs := field.lhs.tail! }
let field := fields.head!
match Lean.isSubobjectField? env s.structName fieldName with
| some substructName =>
let substruct := Struct.mk s.ref substructName #[] substructFields s.source
let substruct ← expandStruct substruct
pure { field with lhs := [field.lhs.head!], val := FieldVal.nested substruct }
| none =>
let updateSource (structStx : Syntax) : TermElabM Syntax := do
let sourcesNew ← s.source.explicit.filterMapM
fun source => mkProjStx? source.stx source.structName fieldName
let explicitSourceStx := if sourcesNew.isEmpty then mkNullNode
else mkSourcesWithSyntax sourcesNew
let implicitSourceStx := s.source.implicit.map (·.stx) |>.getD mkNullNode
return (structStx.setArg 1 explicitSourceStx).setArg 3 implicitSourceStx
let valStx := s.ref -- construct substructure syntax using s.ref as template
let valStx := valStx.setArg 4 mkNullNode -- erase optional expected type
let args := substructFields.toArray.map (·.toSyntax)
let valStx := valStx.setArg 2 (mkNullNode <| mkSepArray args (mkAtom ","))
let valStx ← updateSource valStx
return { field with lhs := [field.lhs.head!], val := FieldVal.term valStx }
/--
Add `val : FieldVal`s to fields as specified by the sources.
If a value is found in the explicit sources (prior to `with`), add it as a `.term` or a
`.nested` `FieldVal`, as appropriate. If not, check for `..`, and make a hole via syntax as a
`.term`. Otherwise, mark the field as as `.missing`.
-/
private partial def addMissingFields (s : Struct) : TermElabM Struct := do
let env ← getEnv
let fieldNames := getStructureFields env s.structName
let ref := s.ref.mkSynthetic
withRef ref do
let fields ← fieldNames.foldlM (init := []) fun fields fieldName => do
match findField? s.fields fieldName with
| some field => return field::fields
| none =>
let addField (val : FieldVal Struct) : TermElabM Fields := do
return { ref, lhs := [FieldLHS.fieldName ref fieldName], val := val } :: fields
match Lean.isSubobjectField? env s.structName fieldName with
| some substructName =>
-- If one of the sources has the subobject field, use it
if let some val ← s.source.explicit.findSomeM?
fun source => mkProjStx? source.stx source.structName fieldName
then
addField (FieldVal.term val)
else
let substruct := Struct.mk ref substructName #[] [] s.source
let substruct ← expandStruct substruct
addField (FieldVal.nested substruct)
| none =>
if let some val ← s.source.explicit.findSomeM?
fun source => mkProjStx? source.stx source.structName fieldName
then
addField (FieldVal.term val)
else
-- Use hole syntax as a term in the natural, no-defaults (`..`) case;
-- otherwise mark it as a missing field.
match s.source.implicit with
| some { isSynthetic := false, useDefaults := false, .. } =>
addField (FieldVal.term (mkHole ref))
| _ => addField FieldVal.missing
return s.setFields fields.reverse
/-- Put the `Struct` into canonical form by expanding different ways of specifying fields
(composite, by index, subobject); group fields by subobject; and incorporate values (or
holes) sources. -/
private partial def expandStruct (s : Struct) : TermElabM Struct := do
let s := expandCompositeFields s
let s ← expandNumLitFields s
let s ← expandParentFields s
let s ← groupFields s
addMissingFields s
end
/-- Information about the constructor. -/
structure CtorHeaderResult where
/-- The constructor function itself as an `Expr`. -/
ctorFn : Expr
/-- The type of the constructor as an `Expr`. -/
ctorFnType : Expr
/-- Metavariables for instances. -/
instMVars : Array MVarId
/-- Named parameters encountered in bindings of the type and the expressions used for them. -/
params : Array (Name × Expr)
/-- Helper function that processes the constructor and its type until its first parameter reaches 0.
-/
private def mkCtorHeaderAux :
Nat → Expr → Expr → Array MVarId → Array (Name × Expr) → TermElabM CtorHeaderResult
| 0, type, ctorFn, instMVars, params =>
return { ctorFn , ctorFnType := type, instMVars, params }
| n+1, type, ctorFn, instMVars, params => do
match (← whnfForall type) with
| .forallE paramName d b c =>
match c with
| .instImplicit =>
let a ← mkFreshExprMVar d .synthetic
mkCtorHeaderAux n (b.instantiate1 a) (mkApp ctorFn a) (instMVars.push a.mvarId!)
(params.push (paramName, a))
| _ =>
let a ← mkFreshExprMVar d
mkCtorHeaderAux n (b.instantiate1 a) (mkApp ctorFn a) instMVars (params.push (paramName, a))
| _ => throwError "unexpected constructor type"
/-- Burrows into the body of a `.forallE` expression `n` times if possible, and returns the result.
If an expression not of the form `.forallE` is encountered along the way, return `none`. -/
private partial def getForallBody : Nat → Expr → Option Expr
| i+1, .forallE _ _ b _ => getForallBody i b
| _+1, _ => none
| 0, type => type
/-- When the expected type is known, attempt to get the type of the constructor by stripping `n`
`.forallE`'s off of the expression and then assigning metavariables by `isDefEq`'ing with
the expected type. -/
private def propagateExpectedType (type : Expr) (numFields : Nat) (expectedType? : Option Expr) :
TermElabM Unit := do
match expectedType? with
| none => return ()
| some expectedType =>
match getForallBody numFields type with
| none => pure ()
| some typeBody =>
unless typeBody.hasLooseBVars do
discard <| isDefEq expectedType typeBody
/-- Process information about a given `ConstructorVal`. -/
private def mkCtorHeader (ctorVal : ConstructorVal) (expectedType? : Option Expr) :
TermElabM CtorHeaderResult := do
let us ← mkFreshLevelMVars ctorVal.levelParams.length
let val := Lean.mkConst ctorVal.name us
let type ← instantiateTypeLevelParams (ConstantInfo.ctorInfo ctorVal) us
let r ← mkCtorHeaderAux ctorVal.numParams type val #[] #[]
propagateExpectedType r.ctorFnType ctorVal.numFields expectedType?
synthesizeAppInstMVars r.instMVars r.ctorFn
return r
/-- Annotate an expression to indicate that it must be synthesized as a default value.
In practice, the expression is a metavariable. -/
def markDefaultMissing (e : Expr) : Expr :=
mkAnnotation `structInstDefault e
/-- Check if an expression has been annotated in a way that indicates it should be synthesized
during the default loop. -/
def defaultMissing? (e : Expr) : Option Expr :=
annotation? `structInstDefault e
/-- Provide a descriptive error message if the structure instance elaboration fails. -/
def throwFailedToElabField {α} (fieldName : Name) (structName : Name) (msgData : MessageData) :
TermElabM α :=
throwError "failed to elaborate field '{fieldName}' of '{structName}, {msgData}"
/-- Attempt to synthesize an -/
def trySynthStructInstance? (s : Struct) (expectedType : Expr) : TermElabM (Option Expr) := do
if !s.allMissing then
return none
else
try synthInstance? expectedType catch _ => return none
-- By Mario Carneiro
/-- Use an expression in syntax. Example: ``(foo $(← toSyntax e))`.
This works by creating syntax for a metavariable, then elaborating that syntax and assigning
the metavariable to the expression in question.
-/
def toSyntax (e : Expr) (type? : Option Expr := none) : TermElabM Syntax := withFreshMacroScope do
let stx ← `(?a)
let mvar ← elabTerm stx type?
mvar.mvarId!.assign e
pure stx
/--
The result of running `elabStruct` on a `Struct`, containing:
* `struct : Struct`, now with updated `expr?` values in its fields, representing the values for
those fields.
* `val : Expr`, the constructor applied to the field values (`expr?`s). This is the actual
expression that the structure instance elaborates to. (Note that this is distinct from the
`val` of each field, which is a `FieldVal`.)
* `instMVars : Array MVarId`, used forkeeping track of instances.
-/
structure ElabStructResult where
/-- The structure's constructor applied to the field values (`expr?`s). This is the actual
expression that the structure instance elaborates to. -/
val : Expr
/-- The `struct` that was fed to `elabStruct`, but now with updated `expr?` values for all of its
fields containing their values as expressions. -/
struct : Struct
/-- Used for keeping track of instances. -/
instMVars : Array MVarId
/--
Elaborates a `Struct` into an `ElabStructResult`.
This computes expressions for all fields of a structure (in `expr?`) on the basis of `FieldVal`s
while simultaneously building the elaboration of the structure instance itself, in the form of
its constructor applied to the expressions for each of its fields in turn.
`.term stx` `FieldVals` are elaborated while ensuring the type (as given by the constructor's
type), `.nested s` `FieldVals` are recursed into. `.missing` `FieldVals` are replaced with
metavariables and annotated to indicate that they will get assigned during the default synthesis
loop. Note that in this case the same metavariable is used both in the `expr?` field and the
final constructed expression (`val`), so that assigning it gives access to the value in both
places. The one exception is if an `autoParam` is encountered in the type, in which case the
tactic is elaborated.
-/
private partial def elabStruct (s : Struct) (expectedType? : Option Expr) :
TermElabM ElabStructResult := withRef s.ref do
let env ← getEnv
let vhc? := s.source.implicit
let ctorVal := getStructureCtor env s.structName
if isPrivateNameFromImportedModule env ctorVal.name then
throwError "invalid \{...} notation, constructor for `{s.structName}` is marked as private"
-- We store the parameters at the resulting `Struct`.
-- We use this information during default value propagation.
let { ctorFn, ctorFnType, params, .. } ← mkCtorHeader ctorVal expectedType?
let (e, _, fields, instMVars) ← s.fields.foldlM
(init := (ctorFn, ctorFnType, [], #[]))
fun (e, type, fields, instMVars) field => do
match field.lhs with
| [.fieldName ref fieldName] =>
let type ← whnfForall type
trace[Elab.struct] "elabStruct {field}, {type}"
match type with
| .forallE _ d b bi =>
let cont (val : Expr) (field : Field Struct) (instMVars := instMVars)
(updateField := true) : TermElabM (Expr × Expr × Fields × Array MVarId) := do
pushInfoTree <| InfoTree.node (children := {}) <| Info.ofFieldInfo {
projName := s.structName.append fieldName,
fieldName,
lctx := (← getLCtx),
val,
stx := ref }
let e := mkApp e val
let type := b.instantiate1 val
let field := if updateField then { field with expr? := some val } else field
return (e, type, field::fields, instMVars)
match field.val with
| .term stx =>
cont (← elabTermEnsuringType stx d.consumeTypeAnnotations) field
| .nested s =>
let inst? := if vhc?.all (·.useDefaults) then
(← trySynthStructInstance? s d) else none
match inst? with
| some val =>
cont val { field with val := FieldVal.term (mkHole field.ref) }
| none =>
let { val, struct := sNew, instMVars := instMVarsNew } ← elabStruct s (some d)
let val ← ensureHasType d val
cont val { field with val := FieldVal.nested sNew } (instMVars ++ instMVarsNew)
| .missing =>
match d.getAutoParamTactic? with
| some (.const tacticDecl ..) =>
let d := (d.getArg! 0).consumeTypeAnnotations
if vhc?.all (·.useDefaults) then
match evalSyntaxConstant env (← getOptions) tacticDecl with
| .error err => throwError err
| .ok tacticSyntax =>
if vhc?.isSome then
let val := (← mkFreshExprMVar (some d) .synthetic)
let stx ← `(by first | $tacticSyntax | exact $(← toSyntax val))
cont (← elabTermEnsuringType stx d)
{field with expr? := some (markDefaultMissing val)}
(updateField := false)
else
let stx ← `(by $tacticSyntax)
cont (← elabTermEnsuringType stx d) field
else
let val ← withRef field.ref <| mkFreshExprMVar (some d)
cont (markDefaultMissing val) field
| _ =>
if bi == .instImplicit then
let val ← withRef field.ref <| mkFreshExprMVar d .synthetic
trace[Elab.struct] ".instImplicit ({val})"
cont val field (instMVars.push val.mvarId!)
else
let val ← withRef field.ref <| mkFreshExprMVar (some d)
cont (markDefaultMissing val) field
| _ => withRef field.ref (throwFailedToElabField
fieldName s.structName m!"unexpected constructor type{indentExpr type}")
| _ => throwErrorAt field.ref "unexpected unexpanded structure field"
return { val := e, struct := s.setFields fields.reverse |>.setParams params, instMVars }
namespace ImplicitFields
/-- Updated as we search for default values. We must search for default values overriden in derived
structures. -/
structure Context where
/-- `Struct`s in the context which might supply default values. -/
structs : Array Struct := #[]
/-- The names of structures in the context which might supply default values. -/
allStructNames : Array Name := #[]
/--
Consider the following example:
```
structure A where
x : Nat := 1
structure B extends A where
y : Nat := x + 1
x := y + 1
structure C extends B where
z : Nat := 2*y
x := z + 3
```
And we are trying to elaborate a structure instance for `C`.
There are default values for `x` at `A`, `B`, and `C`.
We say the default value at `C` has distance 0, the one at `B` distance 1, and the one at `A`
distance 2.
The field `maxDistance` specifies the maximum distance considered in a round of Default field
computation.
Remark: since `C` does not set a default value of `y`, the default value at `B` is at distance 0.
The fixpoint for setting default values works in the following way.
- Keep computing default values using `maxDistance == 0`.
- We increase `maxDistance` whenever we failed to compute a new default value in a round.
- If `maxDistance > 0`, then we interrupt a round as soon as we compute some default value.
We use depth-first search.
- We sign an error if no progress is made when `maxDistance` == structure hierarchy depth (2 in
the example above).
-/
maxDistance : Nat := 0
/-- Stores an indicator of whether progress has been made during a round in the default loop. -/
structure State where
/-- Indicates whether progress has been made during a round in the default loop. -/
progress : Bool := false
/-- Collects the names of all nested structures in a `Struct` (at any depth), including the name of
the structure itself. -/
partial def collectStructNames (struct : Struct) (names : Array Name) : Array Name :=
let names := names.push struct.structName
struct.fields.foldl (init := names) fun names field =>
match field.val with
| .nested struct => collectStructNames struct names
| _ => names
/-- Gets the maximum depth at which any structure is nested within the given structure, i.e. the
height of its subobject poset. -/
partial def getHierarchyDepth (struct : Struct) : Nat :=
struct.fields.foldl (init := 0) fun max field =>
match field.val with
| .nested struct => Nat.max max (getHierarchyDepth struct + 1)
| _ => max
/-- (Monadic) Checks if the value of a field (`expr?`) is an unassigned metavariable that is
annotated to indicate that it should be synthesized during the default loop. -/
def isDefaultMissing? [Monad m] [MonadMCtx m] (field : Field Struct) : m Bool := do
if let some expr := field.expr? then
if let some (.mvar mvarId) := defaultMissing? expr then
unless (← mvarId.isAssigned) do
return true
return false
/-- (Monadic) Gets the first encountered field in a `Struct` whose value (`expr?`) is an unassigned
metavariable that is annotated to indicate that it should be synthesized during the default
loop. -/
partial def findDefaultMissing? [Monad m] [MonadMCtx m] (struct : Struct) :
m (Option (Field Struct)) :=
struct.fields.findSomeM? fun field => do
match field.val with
| .nested struct => findDefaultMissing? struct
| _ => return if (← isDefaultMissing? field) then field else none
/-- (Monadic) Gets an array containing all fields in the `Struct` whose value (`expr?`) is an
unassigned metavariable that is annotated to indicate that it should be synthesized during the
default loop. -/
partial def allDefaultMissing [Monad m] [MonadMCtx m] (struct : Struct) :
m (Array (Field Struct)) :=
go struct *> get |>.run' #[]
where
/-- Loop through all fields in the `Struct`, recursing if a `.nested` one is found, and storing
the field in a mutable array if it `isDefaultMissing?` -/
go (struct : Struct) : StateT (Array (Field Struct)) m Unit :=
for field in struct.fields do
if let .nested struct := field.val then
go struct
else if (← isDefaultMissing? field) then
modify (·.push field)
/-- Gets the name of a field, assuming that its `lhs` is of the form `[.fieldName _ fieldName]`.
Panics otherwise. -/
def getFieldName (field : Field Struct) : Name :=
match field.lhs with
| [.fieldName _ fieldName] => fieldName
| _ => unreachable!
/-- Abbreviation for `ReaderT Context (StateRefT State TermElabM)`: A monad transformation of
`TermElabM` that lets us access the `Context` (relevant for checking if default values are
overridden) and keeping track of whether progress has been made during a round of the default
loop (`State`). -/
abbrev M := ReaderT Context (StateRefT State TermElabM)
/-- Checks if the round has completed by checking that progress has been made and that the
`maxDistance > 0`. -/
def isRoundDone : M Bool := do
return (← get).progress && (← read).maxDistance > 0
/-- Gets the value (`expr?`) of a field in a `Struct` given the name of the field. -/
def getFieldValue? (struct : Struct) (fieldName : Name) : Option Expr :=
struct.fields.findSome? fun field =>
if getFieldName field == fieldName then
field.expr?
else
none
section NamedGoalsWithMetadata
/-- A convenient representation of the metadata attached to named goals produced by `?..` syntax. -/
structure FieldHoleMData where
/-- The index of the named goal used for name conflict resolution when dealing with multiple
occcurrences of `?..`. Each conflicting use of `?..` should generate field holes with
different indices. An index of `0` indicates that no name conflicts were found with any
existing goals. -/
index : Nat
/-- The syntax of the structure instance that contained the `?..` syntax. -/
structRef : Syntax
/-- The name of the structure that contained the `?..` syntax. -/
structName : Name
/-- The name of the field this goal represents. -/
fieldName : Name
/-- The name to be prefixed to the name of this goal. `Name.anonymous` indicates that no name is
to be prefixed. -/
prefixName : Name
/-- Creates the metadata for a field's named goal given the field, the `Struct`, and the
conflict-resolution index. -/
def mkFieldHoleMData (index : Nat) (field : Field Struct) (struct : Struct) : FieldHoleMData :=
{
index,
structRef := struct.ref
structName := struct.structName
fieldName := getFieldName field
prefixName := match struct.source.implicit with
| some {name := some prefixName, ..} => prefixName
| _ => Name.anonymous
}
open KVMap in
/-- Gets the field hole metadata from a metavariable if present. -/
def getFieldHoleMDataFromMVar? (decl : MetavarDecl) : Option FieldHoleMData :=
match decl.type with
| .mdata md _ =>
if getBool md `fieldHole then
some
{
index := getNat md `index
structRef := getSyntax md `structRef
structName := getName md `structName
fieldName := getName md `fieldName
prefixName := getName md `prefixName
}
else none
| _ => none
/-- Checks if a metavariable decl is a named field hole created by `?..` syntax. -/
def isFieldHole (decl : MetavarDecl) : Bool :=
match decl.type with
| .mdata md _ => KVMap.getBool md `fieldHole
| _ => false
section KVMap
/-- Merges two `KVMap`s, overwriting the values of any shared keys with those in the second `KVMap`
-/
def mergeKVMap : KVMap → KVMap → KVMap :=
fun m₀ m₁ => Id.run do
let mut m := m₀
for (name, data) in m₁ do
m := KVMap.insert m name data
return m
/-- Turns a list of key-value pairs (e.g. ``[(`a, ofBool true), (`b, ofNat 2), ...]``) into a
`KVMap`. -/
def toKVMap : List (Name × DataValue) → KVMap
| l => l.foldl (fun m (n, d) => KVMap.insert m n d) {}
end KVMap
open DataValue in
/-- Turns a representation of field hole metadata into actual metadata (a `KVMap`). -/
def mkFieldHoleMDataKVMap (f : FieldHoleMData) : KVMap :=
toKVMap [
(`fieldHole , ofBool true ),
(`index , ofNat f.index ),
(`structRef , ofSyntax f.structRef ),
(`structName , ofName f.structName),
(`fieldName , ofName f.fieldName ),
(`prefixName , ofName f.prefixName)
]
/--
Create a metavariable with `metadata` attached to its `type`.
If there's any existing metadata on `type`, `metadata` is preferentially merged into it.
-/
def mkFreshExprMVarWithMData (type : Expr) (metadata : KVMap) (kind : MetavarKind := default)
(userName := Name.anonymous) : MetaM Expr :=
let annotatedType :=
match type with
| .mdata m e =>
let merge := mergeKVMap m metadata
Expr.mdata merge e
| _ =>
Expr.mdata metadata type
mkFreshExprMVar annotatedType (kind := kind) (userName := userName)
/-- Make a fresh expression metavariable for a field, named accordingly, and with metadata
attached. -/
def mkFreshFieldNamedMVar (type : Expr) (index : Nat) (prefixName : Option Name)
(field : Field Struct) (struct : Struct) : MetaM Expr :=
let fieldHoleMData := mkFieldHoleMDataKVMap <| mkFieldHoleMData index field struct
let name :=
match prefixName with
| some x => x ++ (getFieldName field)
| none => getFieldName field
let name := if index == 0 then name else name.appendIndexAfter index
mkFreshExprMVarWithMData type fieldHoleMData (kind := .syntheticOpaque) (userName := name)
/-- Given the names of two structures, check if they have any field names in common. -/
def fieldsOverlap (env : Environment) (structName₀ : Name) (structName₁ : Name) : Bool :=
let fields₀ := getStructureFieldsFlattened env structName₀ false
let fields₁ := getStructureFieldsFlattened env structName₁ false
fields₀.any (fun field => fields₁.contains field)
-- Monadic to enable tracing.
/-- If the provided metavariable decl is a named field hole created by `?..` syntax, check if it
conflicts with the current structure and prefix name. If so, return its index. Otherwise,
return `none`. -/
def getConflictingIndex? (env : Environment) (s : Struct) (prefixName : Name) (decl : MetavarDecl)
: TermElabM (Option Nat) := do
let fieldHoleMData? := getFieldHoleMDataFromMVar? decl
match fieldHoleMData? with
| some fieldHoleMData =>
let cond2 := prefixName == fieldHoleMData.prefixName
let cond3 := fieldsOverlap env s.structName (fieldHoleMData.structName)
trace[Elab.struct]
"goal name conflict for {fieldHoleMData.structName}: {cond2} && {cond3}"
if cond2 && cond3
then return some fieldHoleMData.index
else return none
| none => return none
/-- Get the next non-conflicting index among all metavariable conflicts.
A metavariable conflicts iff all of the following are true:
* it is a named field hole created by `?..` syntax
* it is not from the same occurrence of `?..`
* it has the same prefix name (possibly `Name.anonymous` if it does not have a prefix)
* it belongs to a structure that has field names in common with the current structure
Note that this gets the index one greater than the maximum conflicting index, not the next
"available" index. We take a "wide berth" approach to avoid situations where it might appear
like two goals are from the same occurrence of `?..` despite this not being the case. -/
def nextIndexGivenCollisions (env : Environment) (mctx : MetavarContext) (s : Struct)
: TermElabM Nat := do
let prefixName := match s.source.implicit with
| some { name := some prefixName, .. } => prefixName
| _ => Name.anonymous
let conflictingIndex : (Option Nat) ← mctx.decls.foldl
(fun i? _ decl => do
let i'? ← getConflictingIndex? env s prefixName decl
return (Option.merge max (← i?) i'?)) (pure none)
match conflictingIndex with
| some i => return i+1
| none => return 0
/-- Assign all fields which did not get synthesized during the default loop (but which were marked
as such) to appropriately-named field holes with metadata in the case of `?..` syntax (and to
natural holes when the `?` is absent). -/
def assignRemainingDefaultsToFieldHoles (struct : Struct) : TermElabM Unit :=
withRef struct.ref do
match struct.source.implicit with
| some vhc =>
let index ← nextIndexGivenCollisions (← getEnv) (← getMCtx) struct
for field in (← allDefaultMissing struct) do
match field.expr? with
| some expr =>
match defaultMissing? expr with
| some (.mvar mvarId) =>
let type := (← getMVarDecl mvarId).type
if vhc.isSynthetic then
mvarId.assign (← withRef field.ref <|
mkFreshFieldNamedMVar type index vhc.name field struct)
else
let newHole ← withRef field.ref <| mkFreshExprMVar type (kind := .natural)
mvarId.assign newHole
registerMVarErrorHoleInfo newHole.mvarId! struct.ref
| _ => unreachable!
| none => unreachable!
| none => return ()
end NamedGoalsWithMetadata
/-- A helper function that applies lambdas whose parameters are field names to the corresponding
field values until it finds a non-lambda, using propagated parameters instead of field names if
necessary along the way. Returns `none` if it finds a lambda that's not of this form. -/
partial def mkDefaultValueAux? (struct : Struct) : Expr → TermElabM (Option Expr)
| .lam n d b c => withRef struct.ref do
if c.isExplicit then
let fieldName := n
match getFieldValue? struct fieldName with
| none => return none
| some val =>
let valType ← inferType val
if (← isDefEq valType d) then
mkDefaultValueAux? struct (b.instantiate1 val)
else
return none
else
if let some (_, param) := struct.params.find? fun (paramName, _) => paramName == n then
-- Recall that we did not use to have support for parameter propagation here.
if (← isDefEq (← inferType param) d) then
mkDefaultValueAux? struct (b.instantiate1 param)
else
return none
else
let arg ← mkFreshExprMVar d
mkDefaultValueAux? struct (b.instantiate1 arg)
| e =>
if e.isAppOfArity ``id 2 then
return some e.appArg!
else
return some e
/-- If possible, make a default value by applying lambdas in the given constant to the appropriate
field values or propagated parameter values. -/
def mkDefaultValue? (struct : Struct) (cinfo : ConstantInfo) : TermElabM (Option Expr) :=
withRef struct.ref do
let us ← mkFreshLevelMVarsFor cinfo
mkDefaultValueAux? struct (← instantiateValueLevelParams cinfo us)
/-- Reduce default value. It performs beta reduction and projections of the given structures. -/
partial def reduce (structNames : Array Name) (e : Expr) : MetaM Expr := do
match e with
| .lam .. => lambdaLetTelescope e fun xs b => do mkLambdaFVars xs (← reduce structNames b)
| .forallE .. => forallTelescope e fun xs b => do mkForallFVars xs (← reduce structNames b)
| .letE .. => lambdaLetTelescope e fun xs b => do mkLetFVars xs (← reduce structNames b)
| .proj _ i b =>
match (← Meta.project? b i) with
| some r => reduce structNames r
| none => return e.updateProj! (← reduce structNames b)
| .app f .. =>
match (← reduceProjOf? e structNames.contains) with
| some r => reduce structNames r
| none =>
let f := f.getAppFn
let f' ← reduce structNames f
if f'.isLambda then
let revArgs := e.getAppRevArgs
reduce structNames (f'.betaRev revArgs)
else
let args ← e.getAppArgs.mapM (reduce structNames)
return mkAppN f' args
| .mdata _ b =>
let b ← reduce structNames b
if (defaultMissing? e).isSome && !b.isMVar then
return b
else
return e.updateMData! b
| .mvar mvarId =>
match (← getExprMVarAssignment? mvarId) with
| some val => if val.isMVar then pure val else reduce structNames val
| none => return e
| e => return e
/--
Attempt to synthesize the default value for a field, looping through nested structures if
necessary. If a default value is found, assign it to the metavariable that we created for the
field's value back in `elabStruct`, and return `true`. Otherwise return `false`.
-/
partial def tryToSynthesizeDefault (structs : Array Struct) (allStructNames : Array Name)
(maxDistance : Nat) (fieldName : Name) (mvarId : MVarId) : TermElabM Bool :=
let rec loop (i : Nat) (dist : Nat) := do
if dist > maxDistance then
return false
else if h : i < structs.size then
let struct := structs.get ⟨i, h⟩
match getDefaultFnForField? (← getEnv) struct.structName fieldName with
| some defFn =>
let cinfo ← getConstInfo defFn
let mctx ← getMCtx
match (← mkDefaultValue? struct cinfo) with
| none => setMCtx mctx; loop (i+1) (dist+1)
| some val =>
let val ← reduce allStructNames val
match val.find? fun e => (defaultMissing? e).isSome with
| some _ => setMCtx mctx; loop (i+1) (dist+1)
| none =>
let mvarDecl ← getMVarDecl mvarId
let val ← ensureHasType mvarDecl.type val
mvarId.assign val
return true
| _ => loop (i+1) dist
else
return false
loop 0 0
/-- The main loop of `tryToSynthesizeDefault`, which keeps track of which struct out of an array of
all nested structs is being considered, as well as the distance to make sure it doesn't exceed
the `maxDistance` (see the documentation for `Context.maxDistance`). -/
add_decl_doc tryToSynthesizeDefault.loop
/--
A step within the default synthesis loop. We proceed only if the round is not done. We loop
through all fields in the structure, attempting to synthesize a default via
`tryToSynthesizeDefault` when possible. If we succeed, we set `progress := true` in the `State`.
Note: by now, all `expr?`s should be `some expr` from `elabStruct`, even if that `expr` is a
metavariable; as such, we panic if one of them is `none`.
-/
partial def step (struct : Struct) : M Unit :=
unless (← isRoundDone) do
withReader (fun ctx => { ctx with structs := ctx.structs.push struct }) do
for field in struct.fields do
match field.val with
| .nested struct => step struct
| _ => match field.expr? with
| none => unreachable!
| some expr =>
match defaultMissing? expr with
| some (.mvar mvarId) =>
unless (← mvarId.isAssigned) do
let ctx ← read
if (← withRef field.ref (tryToSynthesizeDefault
ctx.structs ctx.allStructNames ctx.maxDistance (getFieldName field) mvarId))
then
modify fun _ => { progress := true }
| _ => pure ()
/--
The workhorse of the default synthesis loop.
If there are no fields left that need to be synthesized during the default loop, we return from
the loop.
Otherwise, when we find a field that ought to be synthesized during the default loop, we take a
`step`. If we've made `progress`, we call `propagateLoop` again and reset the depth to `0`. If we
haven't, we call `propagateLoop` again with a higher depth.
If the depth ever exceeds the hierarchy depth, we know that we've searched all nested structures,
but no default values were to be found. In this case, we either throw an error with the missing
fields, or, if a variadic hole is present (e.g. `?..`), simply return from the loop (at which
point the remaining holes will be assigned by `assignRemainingDefaultsToFieldHoles` ).
-/
partial def propagateLoop (hierarchyDepth : Nat) (d : Nat) (struct : Struct) : M Unit := do
match (← findDefaultMissing? struct) with
| none => return () -- Done
| some field =>
trace[Elab.struct] "propagate [{d}] [field := {field}]: {struct}"
if d > hierarchyDepth then
let missingFields := (← allDefaultMissing struct).map getFieldName
if struct.source.implicit.isSome then
return ()
else
let missingFieldsWithoutDefault :=
let env := (← getEnv)
let structs := (← read).allStructNames
missingFields.filter fun fieldName => structs.all fun struct =>
(getDefaultFnForField? env struct fieldName).isNone
let fieldsToReport :=
if missingFieldsWithoutDefault.isEmpty then missingFields else missingFieldsWithoutDefault
throwErrorAt field.ref
"fields missing: {fieldsToReport.toList.map (s!"'{·}'") |> ", ".intercalate}"
else withReader (fun ctx => { ctx with maxDistance := d }) do
modify fun _ => { progress := false }
step struct
if (← get).progress then
propagateLoop hierarchyDepth 0 struct
else
propagateLoop hierarchyDepth (d+1) struct
/--
The default synthesis loop.
We call our workhorse function `propagateLoop` with appropriate initial values, which implements
the loop itself (unless there is a variadic hole that specifies defaults are not to be used).
Then, if there is a variadic hole, we assign the remaining metavariables that couldn't be
synthesized into default values to (named) field holes.
-/
def propagate (struct : Struct) : TermElabM Unit := do
let hierarchyDepth := getHierarchyDepth struct
let structNames := collectStructNames struct #[]
let vhc? := struct.source.implicit
if vhc?.all (·.useDefaults) then
propagateLoop hierarchyDepth 0 struct { allStructNames := structNames } |>.run' {}
if vhc?.isSome then
assignRemainingDefaultsToFieldHoles struct
end ImplicitFields
/--
The main content of the elaboration, during which we normalize the `Struct`'s form, call
`elabStruct` to compute field values and construct the elaborated expression, run the default
synthesis loop (and provide named field holes if warranted), and synthesize instances. -/
private def elabStructInstAux (stx : Syntax) (expectedType? : Option Expr) (source : Source)
: TermElabM Expr := do
let structName ← getStructName expectedType? source
let struct ← liftMacroM <| mkStructView stx structName source
let struct ← expandStruct struct
trace[Elab.struct] "{struct}"
/- We try to synthesize pending problems with `withSynthesize` combinator before trying to use
default values.
This is important in examples such as
```
structure MyStruct where
{α : Type u}
{β : Type v}
a : α
b : β
#check { a := 10, b := true : MyStruct }
```
were the `α` will remain "unknown" until the default instance for `OfNat` is used to ensure
that `10` is a `Nat`.
TODO: investigate whether this design decision may have unintended side effects or produce
confusing behavior.
-/
let { val := r, struct, instMVars } ← withSynthesize (mayPostpone := true) <|
elabStruct struct expectedType?
trace[Elab.struct] "before propagate {r}"
ImplicitFields.propagate struct
synthesizeAppInstMVars instMVars r
return r
/-- The term elaborator for structure instance syntax that includes variadic holes (`?..`). -/
@[term_elab structInstWithHoles] def elabStructInstWithHoles : TermElab := fun stx expectedType? =>
do
match (← expandNonAtomicExplicitSources stx) with
| some stxNew => withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
| none =>
let sourceView ← getStructSource stx
if let some modifyOp ← isModifyOp? stx then
if sourceView.explicit.isEmpty then
throwError
"invalid \{...} notation, explicit source is required when using '[<index>] := <value>'"
elabModifyOp stx modifyOp sourceView.explicit expectedType?
else
elabStructInstAux stx expectedType? sourceView
section haveFieldProj
/-- `as foo` names the field projection `foo`. -/
declare_syntax_cat asname
/-- `as foo` names the field projection `foo`. -/
syntax "as" ident : asname
-- couldn't write elab "haveFieldProj" f:(ident)? x:("as" n:ident)? : tactic
/--
Once a goal has been created for a structure's field via `?..` syntax, `haveFieldProj` can be
used equivalently to `have <field name>.proj := <projection function>`.
`haveFieldProj f as a` gets the field projection for the field `f` and names it `a`;
`f` and `as a` can both be individually omitted.
-/
elab "haveFieldProj" f:(ident)? x:(asname)? : tactic => do
let fieldHoleMData? := ImplicitFields.getFieldHoleMDataFromMVar? (← Tactic.getMainDecl)
match fieldHoleMData? with
| some fieldHoleMData =>
let fieldName := match f with | some f' => f'.getId | none => fieldHoleMData.fieldName
let structName := fieldHoleMData.structName -- so the error fits on one line
let some projname :=
(getProjFnForField? (← getEnv) fieldHoleMData.structName fieldName) | throwError
"couldn't find projection function for field {fieldName} of structure {structName}"
let name := match x with
| some stx => match stx with
| `(asname|as $n:ident) => n
| _ => mkIdent (fieldName ++ `proj)
| none => mkIdent (fieldName ++ `proj)
Tactic.evalTactic (←`(tactic|have $name := $(mkIdent projname)))
| none => throwError "no field metadata found on the main goal"
end haveFieldProj