Implement GNU C semantics for K&R function definitions that follow a
prototype of the same function, where the promoted parameter types in
the K&R definition are not compatible with the types in the
prototype. Fixes PR2821.


git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@66301 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp
index b25ba43..50113ee 100644
--- a/lib/Sema/SemaDecl.cpp
+++ b/lib/Sema/SemaDecl.cpp
@@ -510,6 +510,14 @@
   Old->invalidateAttrs();
 }
 
+/// Used in MergeFunctionDecl to keep track of function parameters in
+/// C.
+struct GNUCompatibleParamWarning {
+  ParmVarDecl *OldParm;
+  ParmVarDecl *NewParm;
+  QualType PromotedType;
+};
+
 /// MergeFunctionDecl - We just parsed a function 'New' from
 /// declarator D which has the same name and scope as a previous
 /// declaration 'Old'.  Figure out how to resolve this situation,
@@ -617,10 +625,11 @@
   // duplicate function decls like "void f(int); void f(enum X);" properly.
   if (!getLangOptions().CPlusPlus &&
       Context.typesAreCompatible(OldQType, NewQType)) {
+    const FunctionType *OldFuncType = OldQType->getAsFunctionType();
     const FunctionType *NewFuncType = NewQType->getAsFunctionType();
     const FunctionProtoType *OldProto = 0;
     if (isa<FunctionNoProtoType>(NewFuncType) &&
-        (OldProto = OldQType->getAsFunctionProtoType())) {
+        (OldProto = dyn_cast<FunctionProtoType>(OldFuncType))) {
       // The old declaration provided a function prototype, but the
       // new declaration does not. Merge in the prototype.
       llvm::SmallVector<QualType, 16> ParamTypes(OldProto->arg_type_begin(),
@@ -647,12 +656,71 @@
       }
 
       New->setParams(Context, &Params[0], Params.size());
-
-    }
+    } 
 
     return MergeCompatibleFunctionDecls(New, Old);
   }
 
+  // GNU C permits a K&R definition to follow a prototype declaration
+  // if the declared types of the parameters in the K&R definition
+  // match the types in the prototype declaration, even when the
+  // promoted types of the parameters from the K&R definition differ
+  // from the types in the prototype. GCC then keeps the types from
+  // the prototype.
+  if (!getLangOptions().CPlusPlus &&
+      !getLangOptions().NoExtensions &&
+      Old->hasPrototype() && !New->hasPrototype() &&
+      New->getType()->getAsFunctionProtoType() &&
+      Old->getNumParams() == New->getNumParams()) {
+    llvm::SmallVector<QualType, 16> ArgTypes;
+    llvm::SmallVector<GNUCompatibleParamWarning, 16> Warnings;
+    const FunctionProtoType *OldProto 
+      = Old->getType()->getAsFunctionProtoType();
+    const FunctionProtoType *NewProto 
+      = New->getType()->getAsFunctionProtoType();
+    
+    // Determine whether this is the GNU C extension.
+    bool GNUCompatible = 
+      Context.typesAreCompatible(OldProto->getResultType(),
+                                 NewProto->getResultType()) &&
+      (OldProto->isVariadic() == NewProto->isVariadic());
+    for (unsigned Idx = 0, End = Old->getNumParams(); 
+         GNUCompatible && Idx != End; ++Idx) {
+      ParmVarDecl *OldParm = Old->getParamDecl(Idx);
+      ParmVarDecl *NewParm = New->getParamDecl(Idx);
+      if (Context.typesAreCompatible(OldParm->getType(), 
+                                     NewProto->getArgType(Idx))) {
+        ArgTypes.push_back(NewParm->getType());
+      } else if (Context.typesAreCompatible(OldParm->getType(),
+                                            NewParm->getType())) {
+        GNUCompatibleParamWarning Warn 
+          = { OldParm, NewParm, NewProto->getArgType(Idx) };
+        Warnings.push_back(Warn);
+        ArgTypes.push_back(NewParm->getType());
+      } else
+        GNUCompatible = false;
+    }
+
+    if (GNUCompatible) {
+      for (unsigned Warn = 0; Warn < Warnings.size(); ++Warn) {
+        Diag(Warnings[Warn].NewParm->getLocation(),
+             diag::ext_param_promoted_not_compatible_with_prototype)
+          << Warnings[Warn].PromotedType
+          << Warnings[Warn].OldParm->getType();
+        Diag(Warnings[Warn].OldParm->getLocation(), 
+             diag::note_previous_declaration);
+      }
+
+      New->setType(Context.getFunctionType(NewProto->getResultType(),
+                                           &ArgTypes[0], ArgTypes.size(),
+                                           NewProto->isVariadic(),
+                                           NewProto->getTypeQuals()));
+      return MergeCompatibleFunctionDecls(New, Old);
+    }
+
+    // Fall through to diagnose conflicting types.
+  }
+
   // A function that has already been declared has been redeclared or defined
   // with a different type- show appropriate diagnostic
   if (unsigned BuiltinID = Old->getBuiltinID(Context)) {