On Oct 17, 2011, at 10:45 PM, David Blaikie wrote:

> I'll be the first to admit I'm not entirely confident that this fix is the 
> right one - but at the very least it's a starting point. It fixes the bug and 
> doesn't regress any other clang tests.
> 
> If there are things this change breaks, I'll be happy to add tests for those 
> cases & work on a fix that accommodates them, or if there's just a more 
> appropriate way to express the required semantics, I'm all ears.
> 
> [basically, members of local structs were being instantiated in the context 
> of their reference, not their definition, and failing because the Decl 
> corresponding to the template of the member wasn't in the context of the 
> instantiation scope (forEach in the attached example). By extending the 
> lifetime of the LocalInstantiationScope and walking up past uncombined scopes 
> in LocalInstantiationScope::findInstantiationOf it's now able to find the 
> relevant Decl & instantiate correctly]

I see why this appears to be working, but it's not actually the right fix. 
Let's walk into what it's actually doing:

Index: lib/Sema/SemaTemplateInstantiateDecl.cpp
===================================================================
--- lib/Sema/SemaTemplateInstantiateDecl.cpp    (revision 142336)
+++ lib/Sema/SemaTemplateInstantiateDecl.cpp    (working copy)
@@ -2540,7 +2540,6 @@
   // This class may have local implicit instantiations that need to be
   // instantiation within this scope.
   PerformPendingInstantiations(/*LocalOnly=*/true);
-  Scope.Exit();

This tells Clang not to exit the instantiation scope, i.e., the thing that maps 
from entities in the template to entities that map into the current 
instantiation. So, we'll end up leaving the mapping for this instantiation 
(doIt<int>) available while we instantiate other templates (including 
completely unrelated templates whose instantiation happens to have shown up in 
the list of instantiations).

Now let's look at:

Index: lib/Sema/SemaTemplateInstantiate.cpp
===================================================================
--- lib/Sema/SemaTemplateInstantiate.cpp        (revision 142336)
+++ lib/Sema/SemaTemplateInstantiate.cpp        (working copy)
@@ -2291,10 +2291,6 @@
       else
         CheckD = 0;
     } while (CheckD);
-    
-    // If we aren't combined with our outer scope, we're done. 
-    if (!Current->CombineWithOuterScope)
-      break;
   }

This change tells Clang to keep looking into outer instantiation scopes when it 
can't find what it's looking for in the current instantiation scope. That 
CombineWithOuterScope barrier is important, however, to make sure that we don't 
ever end up using bindings from other instantiations of the same template that 
are further up on the stack. This can become problematic with, e.g., label 
declarations, which are instantiated on demand.

I don't have a killer test case for you that breaks when you make this change; 
I suspect that that test case is out there, but that it is going to be very 
hard to write. However, let's see how things went wrong:

  - In Sema::MarkDeclarationReferenced, not how around line 9365 we check 
whether the context of an instantiable function is a local class and, if so, 
push it into the set of pending *local* instantiations rather than the set of 
pending (global) instantiations).
  - In Sema::InstantiateFunctionDefinition, we create a LocalInstantiationScope 
and again check for a local class. If we're instantiating something in a local 
class, then we merge our current instantiation scope with its parent scope, 
which turns on that CombineWithOuterScope flag (i.e., skipping the break in the 
second patch snippet above).
  - Late in Sema::InstantiateFunctionDefinition, we instantiate all of the 
pending local instantiations while we're still inside the instantiation scope 
of the original function. So, in combination with the previous bullet, this 
means that those instantiations can access our mappings from local names (e..g, 
Functor) to their instantiations (the instantiated Functor).

Now, things break in your test case because we're forEach<Functor> is not 
classified as a local instantiation, so it doesn't get instantiated while we're 
still inside doIt<int>'s scope. Hence, when forEach<Functor> then triggers an 
instantiation of doIt<int>::Functor::operator(), we're not longer

What your patch is doing is essentially treating *all* instantiations like they 
are local instantiations, so everything gets instantiated while doIt<int> is on 
the current instantiation stack, and all of the instantiation scopes are 
effectively merged.

I *think* the right answer is that, when there are local classes involved in 
the instantiation, we're going to want to keep around the contents of the 
LocalInstantiationScope in some side data structure. Then, when we're 
instantiating something that somehow refers to the local class (or a member of 
it), we'll have to re-create a LocalInstantiationScope as a parent 
LocalInstantiationScope, so that the right set of bindings are available.

        - Doug

_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to