Sema: Properly support Microsoft-mode template arguments

Summary:
There were two things known to be wrong with our implementation of MSVC
mode template arguments:

- We didn't properly handle __uuidof/CXXUuidofExpr and skipped all type
  checking completely.
- We didn't allow for MSVC's extension of allowing certain constant
  "foldable" expressions from showing up in template arguments.
  They allow various casts dereference and address-of operations.
  We can make it more general as we find further peculiarities but this
  is the known extent.

Reviewers: rsmith, doug.gregor, rjmccall

Reviewed By: doug.gregor

CC: cfe-commits, rnk

Differential Revision: http://llvm-reviews.chandlerc.com/D1444

llvm-svn: 189087
This commit is contained in:
David Majnemer 2013-08-23 05:39:39 +00:00
parent 8c8f4325a5
commit 61c39a1ccd
3 changed files with 158 additions and 102 deletions

View File

@ -60,8 +60,8 @@ public:
/// The template argument is a pack expansion of a template name that was
/// provided for a template template parameter.
TemplateExpansion,
/// The template argument is a value- or type-dependent expression
/// stored in an Expr*.
/// The template argument is a value- or type-dependent expression or a
/// non-dependent __uuidof expression stored in an Expr*.
Expression,
/// The template argument is actually a parameter pack. Arguments are stored
/// in the Args struct.

View File

@ -4072,6 +4072,63 @@ isNullPointerValueTemplateArgument(Sema &S, NonTypeTemplateParmDecl *Param,
return NPV_NotNullPointer;
}
/// \brief Checks whether the given template argument is compatible with its
/// template parameter.
static bool CheckTemplateArgumentIsCompatibleWithParameter(
Sema &S, NonTypeTemplateParmDecl *Param, QualType ParamType, Expr *ArgIn,
Expr *Arg, QualType ArgType) {
bool ObjCLifetimeConversion;
if (ParamType->isPointerType() &&
!ParamType->getAs<PointerType>()->getPointeeType()->isFunctionType() &&
S.IsQualificationConversion(ArgType, ParamType, false,
ObjCLifetimeConversion)) {
// For pointer-to-object types, qualification conversions are
// permitted.
} else {
if (const ReferenceType *ParamRef = ParamType->getAs<ReferenceType>()) {
if (!ParamRef->getPointeeType()->isFunctionType()) {
// C++ [temp.arg.nontype]p5b3:
// For a non-type template-parameter of type reference to
// object, no conversions apply. The type referred to by the
// reference may be more cv-qualified than the (otherwise
// identical) type of the template- argument. The
// template-parameter is bound directly to the
// template-argument, which shall be an lvalue.
// FIXME: Other qualifiers?
unsigned ParamQuals = ParamRef->getPointeeType().getCVRQualifiers();
unsigned ArgQuals = ArgType.getCVRQualifiers();
if ((ParamQuals | ArgQuals) != ParamQuals) {
S.Diag(Arg->getLocStart(),
diag::err_template_arg_ref_bind_ignores_quals)
<< ParamType << Arg->getType() << Arg->getSourceRange();
S.Diag(Param->getLocation(), diag::note_template_param_here);
return true;
}
}
}
// At this point, the template argument refers to an object or
// function with external linkage. We now need to check whether the
// argument and parameter types are compatible.
if (!S.Context.hasSameUnqualifiedType(ArgType,
ParamType.getNonReferenceType())) {
// We can't perform this conversion or binding.
if (ParamType->isReferenceType())
S.Diag(Arg->getLocStart(), diag::err_template_arg_no_ref_bind)
<< ParamType << ArgIn->getType() << Arg->getSourceRange();
else
S.Diag(Arg->getLocStart(), diag::err_template_arg_not_convertible)
<< ArgIn->getType() << ParamType << Arg->getSourceRange();
S.Diag(Param->getLocation(), diag::note_template_param_here);
return true;
}
}
return false;
}
/// \brief Checks whether the given template argument is the address
/// of an object or function according to C++ [temp.arg.nontype]p1.
static bool
@ -4099,60 +4156,93 @@ CheckTemplateArgumentAddressOfObjectOrFunction(Sema &S,
break;
}
}
// See through any implicit casts we added to fix the type.
Arg = Arg->IgnoreImpCasts();
// C++ [temp.arg.nontype]p1:
//
// A template-argument for a non-type, non-template
// template-parameter shall be one of: [...]
//
// -- the address of an object or function with external
// linkage, including function templates and function
// template-ids but excluding non-static class members,
// expressed as & id-expression where the & is optional if
// the name refers to a function or array, or if the
// corresponding template-parameter is a reference; or
// In C++98/03 mode, give an extension warning on any extra parentheses.
// See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#773
bool ExtraParens = false;
while (ParenExpr *Parens = dyn_cast<ParenExpr>(Arg)) {
if (!Invalid && !ExtraParens) {
S.Diag(Arg->getLocStart(),
S.getLangOpts().CPlusPlus11 ?
diag::warn_cxx98_compat_template_arg_extra_parens :
diag::ext_template_arg_extra_parens)
<< Arg->getSourceRange();
ExtraParens = true;
}
Arg = Parens->getSubExpr();
}
while (SubstNonTypeTemplateParmExpr *subst =
dyn_cast<SubstNonTypeTemplateParmExpr>(Arg))
Arg = subst->getReplacement()->IgnoreImpCasts();
bool AddressTaken = false;
SourceLocation AddrOpLoc;
if (UnaryOperator *UnOp = dyn_cast<UnaryOperator>(Arg)) {
if (UnOp->getOpcode() == UO_AddrOf) {
Arg = UnOp->getSubExpr();
AddressTaken = true;
AddrOpLoc = UnOp->getOperatorLoc();
if (S.getLangOpts().MicrosoftExt) {
// Microsoft Visual C++ strips all casts, allows an arbitrary number of
// dereference and address-of operators.
Arg = Arg->IgnoreParenCasts();
bool ExtWarnMSTemplateArg = false;
UnaryOperatorKind FirstOpKind;
SourceLocation FirstOpLoc;
while (UnaryOperator *UnOp = dyn_cast<UnaryOperator>(Arg)) {
UnaryOperatorKind UnOpKind = UnOp->getOpcode();
if (UnOpKind == UO_Deref)
ExtWarnMSTemplateArg = true;
if (UnOpKind == UO_AddrOf || UnOpKind == UO_Deref) {
Arg = UnOp->getSubExpr()->IgnoreParenCasts();
if (!AddrOpLoc.isValid()) {
FirstOpKind = UnOpKind;
FirstOpLoc = UnOp->getOperatorLoc();
}
} else
break;
}
}
if (FirstOpLoc.isValid()) {
if (ExtWarnMSTemplateArg)
S.Diag(ArgIn->getLocStart(), diag::ext_ms_deref_template_argument)
<< ArgIn->getSourceRange();
if (isa<CXXUuidofExpr>(Arg)) {
Converted = TemplateArgument(ArgIn);
return false;
}
if (FirstOpKind == UO_AddrOf)
AddressTaken = true;
else if (Arg->getType()->isPointerType()) {
// We cannot let pointers get dereferenced here, that is obviously not a
// constant expression.
assert(FirstOpKind == UO_Deref);
S.Diag(Arg->getLocStart(), diag::err_template_arg_not_decl_ref)
<< Arg->getSourceRange();
}
}
} else {
// See through any implicit casts we added to fix the type.
Arg = Arg->IgnoreImpCasts();
while (SubstNonTypeTemplateParmExpr *subst =
dyn_cast<SubstNonTypeTemplateParmExpr>(Arg))
Arg = subst->getReplacement()->IgnoreImpCasts();
// C++ [temp.arg.nontype]p1:
//
// A template-argument for a non-type, non-template
// template-parameter shall be one of: [...]
//
// -- the address of an object or function with external
// linkage, including function templates and function
// template-ids but excluding non-static class members,
// expressed as & id-expression where the & is optional if
// the name refers to a function or array, or if the
// corresponding template-parameter is a reference; or
// In C++98/03 mode, give an extension warning on any extra parentheses.
// See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#773
bool ExtraParens = false;
while (ParenExpr *Parens = dyn_cast<ParenExpr>(Arg)) {
if (!Invalid && !ExtraParens) {
S.Diag(Arg->getLocStart(),
S.getLangOpts().CPlusPlus11
? diag::warn_cxx98_compat_template_arg_extra_parens
: diag::ext_template_arg_extra_parens)
<< Arg->getSourceRange();
ExtraParens = true;
}
Arg = Parens->getSubExpr();
}
while (SubstNonTypeTemplateParmExpr *subst =
dyn_cast<SubstNonTypeTemplateParmExpr>(Arg))
Arg = subst->getReplacement()->IgnoreImpCasts();
if (UnaryOperator *UnOp = dyn_cast<UnaryOperator>(Arg)) {
if (UnOp->getOpcode() == UO_AddrOf) {
Arg = UnOp->getSubExpr();
AddressTaken = true;
AddrOpLoc = UnOp->getOperatorLoc();
}
}
while (SubstNonTypeTemplateParmExpr *subst =
dyn_cast<SubstNonTypeTemplateParmExpr>(Arg))
Arg = subst->getReplacement()->IgnoreImpCasts();
}
// Stop checking the precise nature of the argument if it is value dependent,
// it should be checked when instantiated.
@ -4160,7 +4250,16 @@ CheckTemplateArgumentAddressOfObjectOrFunction(Sema &S,
Converted = TemplateArgument(ArgIn);
return false;
}
if (isa<CXXUuidofExpr>(Arg)) {
if (CheckTemplateArgumentIsCompatibleWithParameter(S, Param, ParamType,
ArgIn, Arg, ArgType))
return true;
Converted = TemplateArgument(ArgIn);
return false;
}
DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arg);
if (!DRE) {
S.Diag(Arg->getLocStart(), diag::err_template_arg_not_decl_ref)
@ -4305,55 +4404,9 @@ CheckTemplateArgumentAddressOfObjectOrFunction(Sema &S,
}
}
bool ObjCLifetimeConversion;
if (ParamType->isPointerType() &&
!ParamType->getAs<PointerType>()->getPointeeType()->isFunctionType() &&
S.IsQualificationConversion(ArgType, ParamType, false,
ObjCLifetimeConversion)) {
// For pointer-to-object types, qualification conversions are
// permitted.
} else {
if (const ReferenceType *ParamRef = ParamType->getAs<ReferenceType>()) {
if (!ParamRef->getPointeeType()->isFunctionType()) {
// C++ [temp.arg.nontype]p5b3:
// For a non-type template-parameter of type reference to
// object, no conversions apply. The type referred to by the
// reference may be more cv-qualified than the (otherwise
// identical) type of the template- argument. The
// template-parameter is bound directly to the
// template-argument, which shall be an lvalue.
// FIXME: Other qualifiers?
unsigned ParamQuals = ParamRef->getPointeeType().getCVRQualifiers();
unsigned ArgQuals = ArgType.getCVRQualifiers();
if ((ParamQuals | ArgQuals) != ParamQuals) {
S.Diag(Arg->getLocStart(),
diag::err_template_arg_ref_bind_ignores_quals)
<< ParamType << Arg->getType()
<< Arg->getSourceRange();
S.Diag(Param->getLocation(), diag::note_template_param_here);
return true;
}
}
}
// At this point, the template argument refers to an object or
// function with external linkage. We now need to check whether the
// argument and parameter types are compatible.
if (!S.Context.hasSameUnqualifiedType(ArgType,
ParamType.getNonReferenceType())) {
// We can't perform this conversion or binding.
if (ParamType->isReferenceType())
S.Diag(Arg->getLocStart(), diag::err_template_arg_no_ref_bind)
<< ParamType << ArgIn->getType() << Arg->getSourceRange();
else
S.Diag(Arg->getLocStart(), diag::err_template_arg_not_convertible)
<< ArgIn->getType() << ParamType << Arg->getSourceRange();
S.Diag(Param->getLocation(), diag::note_template_param_here);
return true;
}
}
if (CheckTemplateArgumentIsCompatibleWithParameter(S, Param, ParamType, ArgIn,
Arg, ArgType))
return true;
// Create the template argument.
Converted = TemplateArgument(cast<ValueDecl>(Entity->getCanonicalDecl()),

View File

@ -95,10 +95,10 @@ void template_uuid()
}
template <class T, const GUID* g = &__uuidof(T)>
template <class T, const GUID* g = &__uuidof(T)> // expected-note {{template parameter is declared here}}
class COM_CLASS_TEMPLATE { };
typedef COM_CLASS_TEMPLATE<struct_with_uuid, &__uuidof(struct_with_uuid)> COM_TYPE_1;
typedef COM_CLASS_TEMPLATE<struct_with_uuid, &*&__uuidof(struct_with_uuid)> COM_TYPE_1; // expected-warning {{non-type template argument containing a dereference operation is a Microsoft extension}}
typedef COM_CLASS_TEMPLATE<struct_with_uuid> COM_TYPE_2;
template <class T, const GUID& g>
@ -112,6 +112,9 @@ typedef COM_CLASS_TEMPLATE_REF<struct_with_uuid, __uuidof(struct_with_uuid)> COM
}
struct __declspec(uuid("000000A0-0000-0000-C000-000000000049")) late_defined_uuid;
COM_CLASS_TEMPLATE_REF<int, __uuidof(struct_with_uuid)> good_template_arg;
COM_CLASS_TEMPLATE<int, __uuidof(struct_with_uuid)> bad_template_arg; // expected-error {{non-type template argument of type 'const _GUID' is not a constant expression}}
class CtorCall {
public: