|
|
|
|
/- 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
|