@@ -195,10 +195,31 @@ fn check_undefined_nt(grammar: &Grammar, diag: &mut Diagnostics) {
195
195
/// This is intended to help catch any unexpected misspellings, orphaned
196
196
/// productions, or general mistakes.
197
197
fn check_unexpected_roots ( grammar : & Grammar , diag : & mut Diagnostics ) {
198
+ // `set` starts with every production name.
198
199
let mut set: HashSet < _ > = grammar. name_order . iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
199
- grammar. visit_nt ( & mut |nt| {
200
- set. remove ( nt) ;
201
- } ) ;
200
+ fn remove ( set : & mut HashSet < & str > , grammar : & Grammar , prod : & Production , root_name : & str ) {
201
+ prod. expression . visit_nt ( & mut |nt| {
202
+ // Leave the root name in the set if we find it recursively.
203
+ if nt == root_name {
204
+ return ;
205
+ }
206
+ if !set. remove ( nt) {
207
+ return ;
208
+ }
209
+ if let Some ( nt_prod) = grammar. productions . get ( nt) {
210
+ remove ( set, grammar, nt_prod, root_name) ;
211
+ }
212
+ } ) ;
213
+ }
214
+ // Walk the productions starting from the root nodes, and remove every
215
+ // non-terminal from `set`. What's left must be the set of roots.
216
+ grammar
217
+ . productions
218
+ . values ( )
219
+ . filter ( |prod| prod. is_root )
220
+ . for_each ( |root| {
221
+ remove ( & mut set, grammar, root, & root. name ) ;
222
+ } ) ;
202
223
let expected: HashSet < _ > = grammar
203
224
. productions
204
225
. values ( )
@@ -210,17 +231,18 @@ fn check_unexpected_roots(grammar: &Grammar, diag: &mut Diagnostics) {
210
231
if !new. is_empty ( ) {
211
232
warn_or_err ! (
212
233
diag,
213
- "New grammar production detected that is not used in any other \n \
234
+ "New grammar production detected that is not used in any root-accessible \n \
214
235
production. If this is expected, mark the production with\n \
215
236
`@root`. If not, make sure it is spelled correctly and used in\n \
216
- another production.\n \
237
+ another root-accessible production.\n \
217
238
\n \
218
239
The new names are: {new:?}\n "
219
240
) ;
220
241
} else if !removed. is_empty ( ) {
221
242
warn_or_err ! (
222
243
diag,
223
- "Old grammar production root seems to have been removed.\n \
244
+ "Old grammar production root seems to have been removed\n \
245
+ (it is used in some other production that is root-accessible).\n \
224
246
If this is expected, remove `@root` from the production.\n \
225
247
\n \
226
248
The removed names are: {removed:?}\n "
0 commit comments