[cfe-commits] [patch] Friend semantics

Douglas Gregor dgregor at apple.com
Wed Aug 5 14:42:46 PDT 2009


Hi John,

On Aug 4, 2009, at 2:49 PM, John McCall wrote:
> This patch implements many, but not all, of the semantics required  
> for friend declarations.  There's a lot left to be done, but it at  
> least gets friend operators passing Sema.

  Very cool.

> I'd appreciate a review, since I'm interacting with lookup / etc. in  
> ways that I haven't touched before.

+def err_introducing_special_friend : Error<
+  "cannot declare a %select{constructor|destructor|conversion  
operator}0"
+  " at namespace scope">;

I find this diagnostic confusing, because the user has written  
something like

	friend operator int() const;

inside a class. From the language perspective, the diagnostic is  
correct. However, I user probably wants to know that he should write a  
nested-name-specifier before the "operator", e.g.,

	friend X::operator int() const;

How about using a diagnostic such as:

   "friend declaration for a %select{constructor|destructor|conversion  
operator}0 should use a qualified name to refer to a member of another  
class"

+Sema::DeclPtrTy Sema::ActOnFriendDecl(Scope *S,
+                       llvm::PointerUnion<const  
DeclSpec*,Declarator*> DU) {

Nice use of llvm::PointerUnion :)

+      if (RD) {
+        Diag(DS.getFriendSpecLoc(), diag::err_unelaborated_friend_type)
+          << (RD->isUnion())
+          << CodeModificationHint::CreateInsertion 
(DS.getTypeSpecTypeLoc(),
+                                      RD->isUnion() ? " union" : "  
class");
+        return DeclPtrTy::make(RD);
+      }

Excellent!

+  SourceLocation Loc = D->getIdentifierLoc();
+  QualType T = GetTypeForDeclarator(*D, S);
+
+  // C++ [class.friend]p1
+  //   A friend of a class is a function or class....
+  if (!T->isFunctionType()) {
+    Diag(Loc, diag::err_unexpected_friend);
+
+    // It might be worthwhile to try to recover by creating an
+    // appropriate declaration.
+    return DeclPtrTy();
    }

Do we need to check whether T is actually a typedef of a function type?

+  // Recover from invalid scope qualifiers as if they just weren't  
there.
+  if (!ScopeQual.isInvalid() && ScopeQual.isSet()) {
+    DC = computeDeclContext(ScopeQual);

computeDeclContext can end up returning NULL, when the scope specifier  
is dependent. For now, we could just exit early in this case and leave  
a FIXME.

+    while (true) {
+      Decl *Dec = LookupQualifiedNameWithType(DC, Name, T);
+      if (Dec) {
+        FD = cast<FunctionDecl>(Dec);
+        break;
+      }
+      if (DC->isFileContext()) break;
+      DC = DC->getParent();
+    }

This should skip over any non-namespace scopes. For example, I suspect  
that this will find Outer::f when it shouldn't:

	struct Outer {
		void f(int);

		struct Inner {
			friend void f(int);
		};
	};			

+    bool Redeclaration = false;
+    NamedDecl *ND = ActOnFunctionDeclarator(S, *D, DC, T,
+                                            /* PrevDecl = */ NULL,
+                                            MultiTemplateParamsArg 
(*this),
+                                            /* isFunctionDef */ false,
+                                            Redeclaration);

We're only doing this when we haven't yet seen the friend declaration,  
and this ends up being a "real" declaration (which we don't want). I  
don't know the right answer, because it depends on the kind of  
representation we pick for friend functions in the AST.

+// Attempts to find a declaration in the given declaration context
+// with exactly the given type.  Returns null if no such declaration
+// was found.
+Decl *Sema::LookupQualifiedNameWithType(DeclContext *DC,
+                                        DeclarationName Name,
+                                        QualType T) {
+  LookupResult result =
+    LookupQualifiedName(DC, Name, LookupOrdinaryName, true);
+
+  for (LookupResult::iterator ir = result.begin(), ie = result.end();
+       ir != ie; ++ir)
+    if (FunctionDecl *CurFD = dyn_cast<FunctionDecl>(*ir))
+      if (CurFD->getType() == T)
+        return CurFD;
+
+  return NULL;
+}
+

I think LookupQualifiedName is going to find more names than we want  
to find. For example, it will look into base classes, which we don't  
need to do. (Or, if we find a result in a base class, we need to  
diagnose it as an error). Here's an example that GCC and EDG both  
reject:

   struct Base {
     void f(int);
   };

   struct Derived : Base { };

   struct Other {
     friend void Derived::f(int);
   };

Also, the type comparison

	if (CurFD->getType() == T)

needs to use canonical types, e.g.,

	if (Context.getCanonicalType(CurFD->getType()) ==  
Context.getCanonicalType(T))

Please go ahead and commit when you're ready.

	- Doug



More information about the cfe-commits mailing list