@@ -223,6 +223,7 @@ pub struct SymbolTable<Name, Var> {
223223 ///
224224 /// [`scopes`]: Self::scopes
225225 cursor : usize ,
226+ lookup_cursor_is_one_behind : bool ,
226227}
227228
228229impl < Name , Var > SymbolTable < Name , Var > {
@@ -232,8 +233,13 @@ impl<Name, Var> SymbolTable<Name, Var> {
232233 /// until another scope is pushed or [`pop_scope`] is called, causing this
233234 /// scope to be removed along with all variables added to it.
234235 ///
236+ /// # PANICS
237+ /// - If the current lookup scope doesn't match the current scope
238+ ///
235239 /// [`pop_scope`]: Self::pop_scope
236240 pub fn push_scope ( & mut self ) {
241+ self . check_lookup_scope_matches_current_scope ( ) ;
242+
237243 // If the cursor is equal to the scope's stack length then we need to
238244 // push another empty scope. Otherwise we can reuse the already existing
239245 // scope.
@@ -250,16 +256,46 @@ impl<Name, Var> SymbolTable<Name, Var> {
250256 ///
251257 /// # PANICS
252258 /// - If the current lexical scope is the root scope
259+ /// - If the current lookup scope doesn't match the current scope
253260 pub fn pop_scope ( & mut self ) {
254261 // Despite the method title, the variables are only deleted when the
255262 // scope is reused. This is because while a clear is inevitable if the
256263 // scope needs to be reused, there are cases where the scope might be
257264 // popped and not reused, i.e. if another scope with the same nesting
258265 // level is never pushed again.
259266 assert ! ( self . cursor != 1 , "Tried to pop the root scope" ) ;
267+ self . check_lookup_scope_matches_current_scope ( ) ;
260268
261269 self . cursor -= 1 ;
262270 }
271+
272+ /// Reduces the lookup scope by one level.
273+ ///
274+ /// # PANICS
275+ /// - If the current lookup scope doesn't match the current scope
276+ pub fn reduce_lookup_scope ( & mut self ) {
277+ self . check_lookup_scope_matches_current_scope ( ) ;
278+ self . lookup_cursor_is_one_behind = true ;
279+ }
280+
281+ /// Resets the lookup scope to the current scope.
282+ ///
283+ /// # PANICS
284+ /// - If the current lookup scope already matches the current scope
285+ pub fn reset_lookup_scope ( & mut self ) {
286+ assert ! (
287+ self . lookup_cursor_is_one_behind,
288+ "current lookup scope already matches the current scope"
289+ ) ;
290+ self . lookup_cursor_is_one_behind = false ;
291+ }
292+
293+ fn check_lookup_scope_matches_current_scope ( & self ) {
294+ assert ! (
295+ !self . lookup_cursor_is_one_behind,
296+ "current lookup scope doesn't match the current scope"
297+ ) ;
298+ }
263299}
264300
265301impl < Name , Var > SymbolTable < Name , Var >
@@ -277,8 +313,11 @@ where
277313 Name : core:: borrow:: Borrow < Q > ,
278314 Q : core:: hash:: Hash + Eq + ?Sized ,
279315 {
316+ let cursor = self
317+ . cursor
318+ . saturating_sub ( self . lookup_cursor_is_one_behind . into ( ) ) ;
280319 // Iterate backwards through the scopes and try to find the variable
281- for scope in self . scopes [ ..self . cursor ] . iter ( ) . rev ( ) {
320+ for scope in self . scopes [ ..cursor] . iter ( ) . rev ( ) {
282321 if let Some ( var) = scope. get ( name) {
283322 return Some ( var) ;
284323 }
@@ -316,6 +355,7 @@ impl<Name, Var> Default for SymbolTable<Name, Var> {
316355 Self {
317356 scopes : vec ! [ FastHashMap :: default ( ) ] ,
318357 cursor : 1 ,
358+ lookup_cursor_is_one_behind : false ,
319359 }
320360 }
321361}
0 commit comments