Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-and-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ jobs:
formalProofKind: c.formalProofKind || null,
formalProofLink: c.formalProofLink || null,
hasSorryFreeProof: false,
proofConditions: c.proofConditions || [],
}));
fs.writeFileSync(
'site/data/conjectures.json',
Expand Down
73 changes: 73 additions & 0 deletions FormalConjectures/Util/Attributes/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,30 @@ theorem a_test_to_sanity_check_some_definition : ¬ FermatLastTheoremWith 1 := b
sorry
```

## The Proof Condition Attribute:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! I think this is a nice idea! How about instead modifying the formal_proof attribute to have an optional conditional keyword in front - this would allow us to write e.g. conditional formal_proof ....

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in some sense it would also be nice to store exactly what the hypothesis used is, since that should be relatively short, perhaps we could add those statements to the file (with a sorry proof of course) and then add the declaration name to the attribute?!

@williamjblair williamjblair Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Paul-Lez , agreed! conditional is now a modifier on formal_proof itself (with no separate attribute), so it reads @[conditional formal_proof using lean4 at "<link>" assuming grh]. conditional without assuming errors, as does assuming without conditional.

@williamjblair williamjblair Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mo271 makes sense to me! assuming takes declaration names, so the hypothesis lives in the file as a statement with a sorry proof and the attribute references it. The idents resolve against the environment (an unknown name errors, hover jumps to the statement), and extract_names would emit the fully qualified names. This is useful as well bc if the hypothesis later gains a sorry-free proof, the attribute warns that the formal proof may no longer need to be conditional.


### Overview
Records an unproven assumption that a formal proof depends on. This is the
structured counterpart to the `conditional_*` naming convention already used in
the repository (e.g. `conditional_artin_primitive_roots`): a proof can be
`sorry`-free and still establish the statement only under a hypothesis such as
GRH, so a reader and the site can see the condition rather than infer it from a
declaration name.

Use it alongside `formal_proof` when the proof it points to is conditional. The
attribute is repeatable, one condition per application.

### Values
- `@[proof_condition "GRH"]` : the proof holds under the Generalized Riemann Hypothesis.
- `@[proof_condition "Maynard–Tao"]` : the proof assumes the Maynard–Tao theorem.

### Usage example
```
@[category research solved, proof_condition "GRH"]
theorem conditional_result (h : type_of% generalized_riemann_hypothesis) : ... := by
sorry
```

## The Problem Subject Attribute

Provides information about the subject of a mathematical problem, via a
Expand Down Expand Up @@ -211,6 +235,28 @@ def addFormalProofEntry {m : Type → Type} [MonadEnv m]
modifyEnv (formalProofExt.addEntry ·
{ declName := declName, proofKind := kind, proofLink := link })

/-- A tag recording an unproven assumption a formal proof depends on. -/
structure ProofConditionTag where
/-- The name of the declaration with the given tag. -/
declName : Name
/-- A human-readable description of the condition (e.g. "GRH"). -/
condition : String
deriving Inhabited, BEq, Hashable, ToExpr

/-- Defines the `proofConditionExt` extension for recording the unproven
assumptions a formal proof depends on. -/
initialize proofConditionExt :
SimplePersistentEnvExtension ProofConditionTag (Std.HashSet ProofConditionTag) ←
registerSimplePersistentEnvExtension {
addImportedFn := fun as => as.foldl Std.HashSet.insertMany {}
addEntryFn := .insert
}

def addProofConditionEntry {m : Type → Type} [MonadEnv m]
(declName : Name) (condition : String) : m Unit :=
modifyEnv (proofConditionExt.addEntry ·
{ declName := declName, condition := condition })

structure SubjectTag where
/-- The name of the declaration with the given tag. -/
declName : Name
Expand Down Expand Up @@ -317,6 +363,25 @@ initialize Lean.registerBuiltinAttribute {
applicationTime := .afterTypeChecking
}

syntax (name := ProofCondition_attr) "proof_condition" str : attr

/-- Records an unproven assumption a formal proof depends on.

Use alongside `formal_proof` when the proof it points to is conditional. The
attribute is repeatable, one condition per application.

Usage: `@[proof_condition "<condition>"]`, e.g. `@[proof_condition "GRH"]`. -/
initialize Lean.registerBuiltinAttribute {
name := `ProofCondition_attr
descr := "Annotation of an unproven assumption a formal proof depends on."
add := fun decl stx _attrKind => do
match stx with
| `(attr| proof_condition $cond) =>
addProofConditionEntry decl cond.getString
| _ => throwUnsupportedSyntax
applicationTime := .afterTypeChecking
}

syntax subjectList := many(num)

/-- Converts a syntax node to an array of `AMS` subjects.
Expand Down Expand Up @@ -395,6 +460,14 @@ def getFormalProofTag (declName : Name) : m (Option FormalProofTag) := do
let tags ← getFormalProofTags
return tags.find? (·.declName == declName)

def getProofConditionTags : m (Array ProofConditionTag) := do
return proofConditionExt.getState (← MonadEnv.getEnv) |>.toArray

/-- Get the conditions a given declaration's proof depends on. -/
def getProofConditions (declName : Name) : m (Array String) := do
let tags ← getProofConditionTags
return (tags.filter (·.declName == declName)).map (·.condition)

end Helper

/-- Verify that the list of problems contains the expected number of problems
Expand Down
17 changes: 15 additions & 2 deletions scripts/extract_names.lean
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ def getFileLastModified (file : System.FilePath) : IO (Option String) :=
/-- Valid keys for the `--exclude` flag. -/
def validExcludeKeys : List String :=
["docstring", "statement", "subjects", "formalProofKind", "formalProofLink",
"hasSorryFreeProof", "moduleDocstrings", "answerKinds", "fileFirstAdded",
"fileLastModified"]
"hasSorryFreeProof", "moduleDocstrings", "answerKinds", "proofConditions",
"fileFirstAdded", "fileLastModified"]

structure TheoremInfo where
«theorem» : String
Expand All @@ -128,6 +128,7 @@ structure TheoremInfo where
hasSorryFreeProof : Bool
subsets : List String
answerKinds : List String
proofConditions : List String
fileFirstAdded : Option String
fileLastModified : Option String

Expand All @@ -150,6 +151,8 @@ def TheoremInfo.toFilteredJson (info : TheoremInfo) (exclude : Std.HashSet Strin
++ (if info.subsets.isEmpty then [] else [("subsets", toJson info.subsets)])
++ (if exclude.contains "answerKinds" then [] else
[("answerKinds", toJson info.answerKinds)])
++ (if exclude.contains "proofConditions" || info.proofConditions.isEmpty then [] else
[("proofConditions", toJson info.proofConditions)])
++ (if exclude.contains "fileFirstAdded" then [] else
[("fileFirstAdded", toJson info.fileFirstAdded)])
++ (if exclude.contains "fileLastModified" then [] else
Expand Down Expand Up @@ -228,6 +231,7 @@ unsafe def main (args : List String) : IO Unit := do
let tags ← getTags
let subjectTags ← getSubjectTags
let formalProofTags ← getFormalProofTags
let proofConditionTags ← getProofConditionTags

-- Create maps for quick lookup
let mut categoryMap : Std.HashMap Name (List String) := {}
Expand All @@ -241,6 +245,12 @@ unsafe def main (args : List String) : IO Unit := do
for tag in formalProofTags do
formalProofMap := formalProofMap.insert tag.declName tag

-- Create proof condition map (a declaration may carry several conditions)
let mut proofConditionMap : Std.HashMap Name (List String) := {}
for tag in proofConditionTags do
proofConditionMap := proofConditionMap.insert tag.declName
(tag.condition :: proofConditionMap.getD tag.declName [])

let mut subjectMap : Std.HashMap Name (List String) := {}
for tag in subjectTags do
let subjects := tag.subjects.map (fun (s : AMS) => s!"{s.toNat?.get!}")
Expand Down Expand Up @@ -301,6 +311,8 @@ unsafe def main (args : List String) : IO Unit := do
IO.eprintln s!"WARNING: Theorem {name} is categorised as `API` but has no sorry-free proof"
| _, _ => pure ()
let subsets := (theoremToSubsets.getD name []).toArray.qsort (· < ·) |>.toList
let proofConditions :=
(proofConditionMap.getD name []).toArray.qsort (· < ·) |>.toList
-- Determine answerKinds from the elaborated type
let answerKinds ← Meta.MetaM.run'
(getAnswerKinds info.type)
Expand All @@ -318,6 +330,7 @@ unsafe def main (args : List String) : IO Unit := do
hasSorryFreeProof := hasSorryFreeProof,
subsets := subsets
answerKinds := answerKinds
proofConditions := proofConditions
fileFirstAdded := fileFirstAdded
fileLastModified := fileLastModified
} :: allResults
Expand Down
1 change: 1 addition & 0 deletions site/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ function processEntry(entry) {
hasFormalProof,
formalProofKind: entry.formalProofKind || null,
formalProofLink: entry.formalProofLink || null,
proofConditions: entry.proofConditions || [],
};
}

Expand Down
1 change: 1 addition & 0 deletions site/src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ a.cat-stat:hover { text-decoration: none; opacity: 0.8; }
color: #064e3b;
border: 1px solid #6ee7b7;
}
.cat-conditional { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
.cat-textbook { background: var(--cat-grad-bg); color: #1e40af; border: 1px solid #bfdbfe; }
.cat-test { background: var(--cat-other-bg); color: #374151; border: 1px solid #e5e7eb; }
.cat-api { background: var(--cat-other-bg); color: #374151; border: 1px solid #e5e7eb; }
Expand Down
13 changes: 13 additions & 0 deletions site/src/js/theorem.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,15 @@ function renderDetail(theorem, siblings, verso, contributors) {
</div>
</div>` : '';

// Proof conditions: unproven assumptions the formal proof depends on.
const proofConditions = theorem.proofConditions || [];
const proofConditionsSection = proofConditions.length ? `
<div class="theorem-detail__section">
<div class="detail-label">Proof conditions</div>
<div class="detail-value">The formal proof holds under
${proofConditions.map(c => `<code>${FC.escapeHTML(c)}</code>`).join(', ')}.</div>
</div>` : '';

const contributorsSection = contributors.length ? `
<div class="theorem-detail__section">
<div class="detail-label">File contributors</div>
Expand All @@ -507,6 +516,8 @@ function renderDetail(theorem, siblings, verso, contributors) {
<header class="theorem-detail__header">
<h1 class="theorem-detail__title">${FC.escapeHTML(theorem.displayTheorem)}</h1>
<span class="badge ${catMeta.css}" style="font-size:.9rem;padding:.3rem .9rem">${FC.escapeHTML(catMeta.label)}</span>
${proofConditions.length ? `<span class="badge cat-conditional" style="font-size:.9rem;padding:.3rem .9rem"
title="The formal proof depends on an unproven assumption">Conditional</span>` : ''}
</header>

${moduleDocSection}
Expand All @@ -515,6 +526,8 @@ function renderDetail(theorem, siblings, verso, contributors) {

${codeSection}

${proofConditionsSection}

${contributorsSection}

<div class="theorem-detail__section">
Expand Down
Loading