@@ -195,10 +195,31 @@ fn check_undefined_nt(grammar: &Grammar, diag: &mut Diagnostics) {
195195/// This is intended to help catch any unexpected misspellings, orphaned
196196/// productions, or general mistakes.
197197fn check_unexpected_roots ( grammar : & Grammar , diag : & mut Diagnostics ) {
198+ // `set` starts with every production name.
198199 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+ } ) ;
202223 let expected: HashSet < _ > = grammar
203224 . productions
204225 . values ( )
@@ -210,17 +231,18 @@ fn check_unexpected_roots(grammar: &Grammar, diag: &mut Diagnostics) {
210231 if !new. is_empty ( ) {
211232 warn_or_err ! (
212233 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 \
214235 production. If this is expected, mark the production with\n \
215236 `@root`. If not, make sure it is spelled correctly and used in\n \
216- another production.\n \
237+ another root-accessible production.\n \
217238 \n \
218239 The new names are: {new:?}\n "
219240 ) ;
220241 } else if !removed. is_empty ( ) {
221242 warn_or_err ! (
222243 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 \
224246 If this is expected, remove `@root` from the production.\n \
225247 \n \
226248 The removed names are: {removed:?}\n "
0 commit comments