@@ -55,98 +55,185 @@ open Lean System
5555
5656namespace AutoLabel
5757
58+ /-- Mathlib's Github topic labels -/
59+ inductive Label where
60+ | «t-algebra »
61+ | «t-algebraic-geometry »
62+ | «t-algebraic-topology »
63+ | «t-analysis »
64+ | «t-category-theory »
65+ | «t-combinatorics »
66+ | «t-computability »
67+ | «t-condensed »
68+ | «t-convex-geometry »
69+ | «t-data »
70+ | «t-differential-geometry »
71+ | «t-dynamics »
72+ | «t-euclidean-geometry »
73+ | «t-geometric-group-theory »
74+ | «t-group-theory »
75+ | «t-linter »
76+ | «t-logic »
77+ | «t-measure-probability »
78+ | «t-meta »
79+ | «t-number-theory »
80+ | «t-order »
81+ | «t-ring-theory »
82+ | «t-set-theory »
83+ | «t-topology »
84+ | «CI »
85+ | «dependency-bump »
86+ | «IMO »
87+ deriving BEq, Hashable, Repr
88+
89+ /-- Array of all topic labels which are used in Mathlib -/
90+ def mathlibLabels : Array Label := #[
91+ .«t-algebra », .«t-algebraic-geometry », .«t-algebraic-topology », .«t-analysis »,
92+ .«t-category-theory », .«t-combinatorics », .«t-computability », .«t-condensed »,
93+ .«t-convex-geometry », .«t-data », .«t-differential-geometry », .«t-dynamics »,
94+ .«t-euclidean-geometry », .«t-geometric-group-theory », .«t-group-theory », .«t-linter »,
95+ .«t-logic », .«t-measure-probability », .«t-meta », .«t-number-theory », .«t-order »,
96+ .«t-ring-theory », .«t-set-theory », .«t-topology », .«CI », .«dependency-bump », .«IMO »
97+ ]
98+
99+ def Label.toString : Label → String
100+ | .«t-algebra » => "t-algebra"
101+ | .«t-algebraic-geometry » => "t-algebraic-geometry"
102+ | .«t-algebraic-topology » => "t-algebraic-topology"
103+ | .«t-analysis » => "t-analysis"
104+ | .«t-category-theory » => "t-category-theory"
105+ | .«t-combinatorics » => "t-combinatorics"
106+ | .«t-computability » => "t-computability"
107+ | .«t-condensed » => "t-condensed"
108+ | .«t-convex-geometry » => "t-convex-geometry"
109+ | .«t-data » => "t-data"
110+ | .«t-differential-geometry » => "t-differential-geometry"
111+ | .«t-dynamics » => "t-dynamics"
112+ | .«t-euclidean-geometry » => "t-euclidean-geometry"
113+ | .«t-geometric-group-theory » => "t-geometric-group-theory"
114+ | .«t-group-theory » => "t-group-theory"
115+ | .«t-linter » => "t-linter"
116+ | .«t-logic » => "t-logic"
117+ | .«t-measure-probability » => "t-measure-probability"
118+ | .«t-meta » => "t-meta"
119+ | .«t-number-theory » => "t-number-theory"
120+ | .«t-order » => "t-order"
121+ | .«t-ring-theory » => "t-ring-theory"
122+ | .«t-set-theory » => "t-set-theory"
123+ | .«t-topology » => "t-topology"
124+ | .«CI » => "CI"
125+ | .«IMO » => "IMO"
126+ | .«dependency-bump » => "dependency-bump"
127+
128+ instance : ToString Label where
129+ toString := Label.toString
130+
131+ -- test to ensure all labels are listed in `mathlibLabels`
132+ open Lean in
133+ run_cmd
134+ let some (.inductInfo labels) := (← getEnv).find? ``Label | unreachable!
135+ let labelNames := labels.ctors.filterMap (·.components.getLast?) |>.toArray
136+ let some (.defnInfo dinfo) := (← getEnv).find? ``mathlibLabels | unreachable!
137+ let allConstants := dinfo.value.getUsedConstants
138+ let constants := allConstants.filterMap fun n =>
139+ if n.components.dropLast.getLast? == some `Label then n.components.getLast? else none
140+ if labelNames != constants then
141+ let mut out : List String := []
142+ for (a, b) in labelNames.zip constants do
143+ if a != b then
144+ out := s! "expexcted { a} got { b} " :: out
145+ logWarning m! "The available Labels is out of sync with the labels listed in \
146+ { .ofConstName ``mathlibLabels } .\n \
147+ Please keep them sorted and in sync!\n { "\n " .intercalate out.reverse} "
148+
58149/--
59- A `Label` consists of the
60- * The `label` field is the actual GitHub label name.
150+ A `LabelData` consists of the
61151* The `dirs` field is the array of all "root paths" such that a modification in a file contained
62152 in one of these paths should be labelled with `label`.
63153* The `exclusions` field is the array of all "root paths" that are excluded, among the
64154 ones that start with the ones in `dirs`.
65155 Any modifications to a file in an excluded path is ignored for the purposes of labelling.
156+ * The `dependencies` field is the array of all labels, which are lower in the import hierarchy
157+ and which should be excluded if the label is present.
66158 -/
67- structure Label where
68- /-- The label name as it appears on GitHub -/
69- label : String
159+ structure LabelData (label : Label) where
70160 /-- Array of paths which fall under this label. e.g. `"Mathlib" / "Algebra"`.
71161
72162 For a label of the form `t-set-theory` this defaults to `#["Mathlib" / "SetTheory"]`. -/
73- dirs : Array FilePath := if label.startsWith "t-" then
74- #["Mathlib" / ("" .intercalate (label.splitOn "-" |>.drop 1 |>.map .capitalize))]
163+ dirs : Array FilePath := if label.toString. startsWith "t-" then
164+ #["Mathlib" / ("" .intercalate (label.toString. splitOn "-" |>.drop 1 |>.map .capitalize))]
75165 else #[]
76166 /-- Array of paths which should be excluded.
77167 Any modifications to a file in an excluded path are ignored for the purposes of labelling. -/
78168 exclusions : Array FilePath := #[]
79169 deriving BEq, Hashable
80170
81- /--
82- Mathlib labels and their corresponding folders. Add new labels and folders here!
83- -/
84- def mathlibLabels : Array Label := #[
85- { label := "t-algebra" ,
171+ /-- Mathlib labels and their corresponding folders -/
172+ def mathlibLabelData : (l : Label) → LabelData l
173+ | .«t-algebra » => {
86174 dirs := #[
87175 "Mathlib" / "Algebra" ,
88176 "Mathlib" / "FieldTheory" ,
89177 "Mathlib" / "RepresentationTheory" ,
90- "Mathlib" / "LinearAlgebra" ] },
91- { label := " t-algebraic-geometry" ,
178+ "Mathlib" / "LinearAlgebra" ] }
179+ | .« t-algebraic-geometry » => {
92180 dirs := #[
93181 "Mathlib" / "AlgebraicGeometry" ,
94- "Mathlib" / "Geometry" / "RingedSpace" ] },
95- { label := "t-algebraic-topology" ,
96- dirs := #["Mathlib" / "AlgebraicTopology" ] },
97- { label := "t-analysis" },
98- { label := "t-category-theory" },
99- { label := "t-combinatorics" },
100- { label := "t-computability" },
101- { label := "t-condensed" },
102- { label := "t-convex-geometry" ,
103- dirs := #["Mathlib" / "Geometry" / "Convex" ] },
104- { label := "t-data"
182+ "Mathlib" / "Geometry" / "RingedSpace" ] }
183+ | .«t-algebraic-topology » => {}
184+ | .«t-analysis » => {}
185+ | .«t-category-theory » => {}
186+ | .«t-combinatorics » => {}
187+ | .«t-computability » => {}
188+ | .«t-condensed » => {}
189+ | .«t-convex-geometry » => {
190+ dirs := #["Mathlib" / "Geometry" / "Convex" ] }
191+ | .«t-data » => {
105192 dirs := #[
106193 "Mathlib" / "Control" ,
107- "Mathlib" / "Data" ,] },
108- { label := "t-differential-geometry" ,
109- dirs := #["Mathlib" / "Geometry" / "Manifold" ] },
110- { label := "t-dynamics" },
111- { label := "t-euclidean-geometry" ,
112- dirs := #["Mathlib" / "Geometry" / "Euclidean" ] },
113- { label := "t-geometric-group-theory" ,
114- dirs := #["Mathlib" / "Geometry" / "Group" ] },
115- { label := "t-group-theory" ,
116- dirs := #["Mathlib" / "GroupTheory" ] },
117- { label := "t-linter" ,
194+ "Mathlib" / "Data" ] }
195+ | .«t-differential-geometry » => {
196+ dirs := #["Mathlib" / "Geometry" / "Manifold" ] }
197+ | .«t-dynamics » => {}
198+ | .«t-euclidean-geometry » => {
199+ dirs := #["Mathlib" / "Geometry" / "Euclidean" ] }
200+ | .«t-geometric-group-theory » => {
201+ dirs := #["Mathlib" / "Geometry" / "Group" ] }
202+ | .«t-group-theory » => {}
203+ | .«t-linter » => {
118204 dirs := #[
119205 "Mathlib" / "Tactic" / "Linter" ,
120206 "scripts" / "lint-style.lean" ,
121207 "scripts" / "lint-style.py" ,
122- ] },
123- { label := " t-logic" ,
208+ ] }
209+ | .« t-logic » => {
124210 dirs := #[
125211 "Mathlib" / "Logic" ,
126- "Mathlib" / "ModelTheory" ] },
127- { label := " t-measure-probability" ,
212+ "Mathlib" / "ModelTheory" ] }
213+ | .« t-measure-probability » => {
128214 dirs := #[
129215 "Mathlib" / "MeasureTheory" ,
130216 "Mathlib" / "Probability" ,
131- "Mathlib" / "InformationTheory" ] },
132- { label := " t-meta" ,
217+ "Mathlib" / "InformationTheory" ] }
218+ | .« t-meta » => {
133219 dirs := #[
134220 "Mathlib" / "Lean" ,
135221 "Mathlib" / "Mathport" ,
136222 "Mathlib" / "Tactic" ,
137223 "Mathlib" / "Util" ],
138- exclusions := #["Mathlib" / "Tactic" / "Linter" ] },
139- { label := "t-number-theory" },
140- { label := "t-order" },
141- { label := "t-ring-theory" ,
142- dirs := #["Mathlib" / "RingTheory" ] },
143- { label := "t-set-theory" },
144- { label := "t-topology" ,
145- dirs := #["Mathlib" / "Topology" ] },
146- { label := "CI" ,
224+ exclusions := #["Mathlib" / "Tactic" / "Linter" ] }
225+ | .«t-number-theory » => {}
226+ | .«t-order » => {}
227+ | .«t-ring-theory » => {}
228+ | .«t-set-theory » => {}
229+ | .«t-topology » => {}
230+ | .«CI » => {
147231 dirs := #[
148232 ".github" ,
149233 "scripts" ,
234+ "scripts" / "nolints.json" ,
235+ "scripts" / "nolints-style.txt" ,
236+ "scripts" / "nolints_prime_decls.txt" ,
150237 ],
151238 exclusions := #[
152239 "scripts" / "lint-style.lean" ,
@@ -155,11 +242,11 @@ def mathlibLabels : Array Label := #[
155242 "scripts" / "nolints.json" ,
156243 "scripts" / "nolints-style.txt" ,
157244 "scripts" / "nolints_prime_decls.txt" ,
158- ] },
159- { label := "IMO" ,
160- dirs := #["Archive" / "Imo" ] },
161- { label := " dependency-bump" ,
162- dirs := #["lake-manifest.json" ] } ]
245+ ] }
246+ | .« IMO » => {
247+ dirs := #["Archive" / "Imo" ] }
248+ | .« dependency-bump » => {
249+ dirs := #["lake-manifest.json" ] }
163250
164251/-- Exceptions inside `Mathlib/` which are not covered by any label. -/
165252def mathlibUnlabelled : Array FilePath := #[
@@ -174,20 +261,21 @@ def _root_.System.FilePath.isPrefixOf (dir path : FilePath) : Bool :=
174261 (dir / "" ).normalize.toString.isPrefixOf (path / "" ).normalize.toString
175262
176263/--
177- Return all names of labels in `mathlibLabels` which match
264+ Return all labels in `mathlibLabels` which match
178265at least one of the `files`.
179266
180267* `files`: array of relative paths starting from the mathlib root directory.
181268 -/
182- def getMatchingLabels (files : Array FilePath) : Array String :=
269+ def getMatchingLabels (files : Array FilePath) : Array Label :=
183270 let applicable := mathlibLabels.filter fun label ↦
184271 -- first exclude all files the label excludes,
185272 -- then see if any file remains included by the label
273+ let data := mathlibLabelData label
186274 let notExcludedFiles := files.filter fun file ↦
187- label .exclusions.all (!·.isPrefixOf file)
188- label .dirs.any (fun dir ↦ notExcludedFiles.any (dir.isPrefixOf ·))
189- -- return sorted list of label names
190- applicable.map (·.label) |>.qsort (· < ·)
275+ data .exclusions.all (!·.isPrefixOf file)
276+ data .dirs.any (fun dir ↦ notExcludedFiles.any (dir.isPrefixOf ·))
277+ -- return sorted list of labels
278+ applicable |>.qsort (·.toString < ·.toString )
191279
192280/-!
193281Testing the functionality of the declarations defined in this script
@@ -202,22 +290,22 @@ section Tests
202290
203291#guard getMatchingLabels #[] == #[]
204292-- Test default value for `label.dirs` works
205- #guard getMatchingLabels #["Mathlib" / "SetTheory" / "ZFC" ] == #[" t-set-theory" ]
293+ #guard getMatchingLabels #["Mathlib" / "SetTheory" / "ZFC" ] == #[.« t-set-theory » ]
206294-- Test exclusion
207- #guard getMatchingLabels #["Mathlib" / "Tactic" / "Abel.lean" ] == #[" t-meta" ]
208- #guard getMatchingLabels #["Mathlib" / "Tactic" / "Linter" / "Lint.lean" ] == #[" t-linter" ]
295+ #guard getMatchingLabels #["Mathlib" / "Tactic" / "Abel.lean" ] == #[.« t-meta » ]
296+ #guard getMatchingLabels #["Mathlib" / "Tactic" / "Linter" / "Lint.lean" ] == #[.« t-linter » ]
209297#guard getMatchingLabels #[
210298 "Mathlib" / "Tactic" / "Linter" / "Lint.lean" ,
211- "Mathlib" / "Tactic" / "Abel.lean" ] == #[" t-linter" , " t-meta" ]
299+ "Mathlib" / "Tactic" / "Abel.lean" ] == #[.« t-linter », .« t-meta » ]
212300
213301-- Test targeting a file instead of a directory
214- #guard getMatchingLabels #["lake-manifest.json" ] == #[" dependency-bump" ]
302+ #guard getMatchingLabels #["lake-manifest.json" ] == #[.« dependency-bump » ]
215303
216304-- Test linting of specific changes touching linting and CI.
217- #guard getMatchingLabels #["scripts" / "add_deprecations.sh" ] == #["CI" ]
218- #guard getMatchingLabels #["scripts" / "lint-style.lean" ] == #[" t-linter" ]
305+ #guard getMatchingLabels #["scripts" / "add_deprecations.sh" ] == #[.« CI » ]
306+ #guard getMatchingLabels #["scripts" / "lint-style.lean" ] == #[.« t-linter » ]
219307#guard getMatchingLabels #["Mathlib" / "Tactic" / "Linter" / "TextBased.lean" ,
220- "scripts" / "lint-style.lean" , "scripts" / "lint-style.py" ] == #[" t-linter" ]
308+ "scripts" / "lint-style.lean" , "scripts" / "lint-style.py" ] == #[.« t-linter » ]
221309#guard getMatchingLabels #["scripts" / "noshake.json" ] == #[]
222310
223311/-- Testing function to ensure the labels defined in `mathlibLabels` cover all
@@ -282,24 +370,25 @@ unsafe def main (args : List String): IO UInt32 := do
282370 return 1
283371 let prNumber? := args[0 ]?
284372
285- -- test: validate that all paths in `mathlibLabels ` actually exist
373+ -- test: validate that all paths in `mathlibLabelData ` actually exist
286374 let mut valid := true
287375 for label in mathlibLabels do
288- for dir in label.dirs do
376+ let data := mathlibLabelData label
377+ for dir in data.dirs do
289378 unless ← FilePath.pathExists dir do
290379 -- print github annotation error
291380 println <| AutoLabel.githubAnnotation "error" "scripts/autolabel.lean"
292- s! "Misformatted `{ ``AutoLabel.mathlibLabels } `"
293- s! "directory '{ dir} ' does not exist but is included by label '{ label.label } '. \
294- Please update `{ ``AutoLabel.mathlibLabels } `!"
381+ s! "Misformatted `{ ``AutoLabel.mathlibLabelData } `"
382+ s! "directory '{ dir} ' does not exist but is included by label '{ label} '. \
383+ Please update `{ ``AutoLabel.mathlibLabelData } `!"
295384 valid := false
296- for dir in label .exclusions do
385+ for dir in data .exclusions do
297386 unless ← FilePath.pathExists dir do
298387 -- print github annotation error
299388 println <| AutoLabel.githubAnnotation "error" "scripts/autolabel.lean"
300- s! "Misformatted `{ ``AutoLabel.mathlibLabels } `"
301- s! "directory '{ dir} ' does not exist but is excluded by label '{ label.label } '. \
302- Please update `{ ``AutoLabel.mathlibLabels } `!"
389+ s! "Misformatted `{ ``AutoLabel.mathlibLabelData } `"
390+ s! "directory '{ dir} ' does not exist but is excluded by label '{ label} '. \
391+ Please update `{ ``AutoLabel.mathlibLabelData } `!"
303392 valid := false
304393 unless valid do
305394 return 2
@@ -311,7 +400,7 @@ unsafe def main (args : List String): IO UInt32 := do
311400 -- note: only emitting a warning because the workflow is only triggered on the first commit
312401 -- of a PR and could therefore lead to unexpected behaviour if a folder was created later.
313402 println <| AutoLabel.githubAnnotation "warning" "scripts/autolabel.lean"
314- s! "Incomplete `{ ``AutoLabel.mathlibLabels } `"
403+ s! "Incomplete `{ ``AutoLabel.mathlibLabelData } `"
315404 s! "the following paths inside `Mathlib/` are not covered \
316405 by any label: { notMatchedPaths} Please modify `AutoLabel.mathlibLabels` accordingly!"
317406 -- return 3
@@ -339,12 +428,12 @@ unsafe def main (args : List String): IO UInt32 := do
339428 cmd := "gh"
340429 args := #["pr" , "view" , n, "--json" , "labels" , "--jq" , ".labels .[] .name" ]}
341430 let labels := labelsPresent.splitToList (· == '\n ' )
342- let autoLabels := mathlibLabels.map (·.label )
431+ let autoLabels := mathlibLabels.map (·.toString )
343432 match labels.filter autoLabels.contains with
344433 | [] => -- if the PR does not have a label that this script could add, then we add a label
345434 let _ ← IO.Process.run {
346435 cmd := "gh" ,
347- args := #["pr" , "edit" , n, "--add-label" , label] }
436+ args := #["pr" , "edit" , n, "--add-label" , label.toString ] }
348437 println s! "::notice::added label: { label} "
349438 | t_labels_already_present =>
350439 println s! "::notice::Did not add label '{ label} ', since { t_labels_already_present} \
0 commit comments