From: Douglas Gregor Date: Tue, 21 Oct 2008 23:43:52 +0000 (+0000) Subject: Initial step toward supporting qualification conversions (C++ 4.4). X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=98cd599ee8a9b259ed7388ee2921a20d97658864;p=clang Initial step toward supporting qualification conversions (C++ 4.4). Changes: - Sema::IsQualificationConversion determines whether we have a qualification conversion. - Sema::CheckSingleAssignment constraints now follows the C++ rules in C++, performing an implicit conversion from the right-hand side to the type of the left-hand side rather than checking based on the C notion of "compatibility". We now rely on the implicit-conversion code to determine whether the conversion can happen or not. Sema::TryCopyInitialization has an ugly reference-related hack to cope with the initialization of references, for now. - When building DeclRefExprs, strip away the reference type, since there are no expressions whose type is a reference. We'll need to do this throughout Sema. - Expr::isLvalue now permits functions to be lvalues in C++ (but not in C). git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@57935 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/Type.h b/include/clang/AST/Type.h index e2ba317d05..b11f711255 100644 --- a/include/clang/AST/Type.h +++ b/include/clang/AST/Type.h @@ -160,6 +160,27 @@ public: inline QualType getUnqualifiedType() const; + /// isMoreQualifiedThan - Determine whether this type is more + /// qualified than the Other type. For example, "const volatile int" + /// is more qualified than "const int", "volatile int", and + /// "int". However, it is not more qualified than "const volatile + /// int". + bool isMoreQualifiedThan(QualType Other) const { + unsigned MyQuals = this->getCVRQualifiers(); + unsigned OtherQuals = Other.getCVRQualifiers(); + return MyQuals != OtherQuals && (MyQuals | OtherQuals) == MyQuals; + } + + /// isAtLeastAsQualifiedAs - Determine whether this type is at last + /// as qualified as the Other type. For example, "const volatile + /// int" is at least as qualified as "const int", "volatile int", + /// "int", and "const volatile int". + bool isAtLeastAsQualifiedAs(QualType Other) const { + unsigned MyQuals = this->getCVRQualifiers(); + unsigned OtherQuals = Other.getCVRQualifiers(); + return MyQuals | OtherQuals == MyQuals; + } + /// operator==/!= - Indicate whether the specified types and qualifiers are /// identical. bool operator==(const QualType &RHS) const { diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 943cd9a057..d957e33aa1 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -15,6 +15,7 @@ #include "clang/AST/APValue.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/RecordLayout.h" #include "clang/AST/StmtVisitor.h" #include "clang/Basic/TargetInfo.h" @@ -334,14 +335,17 @@ bool Expr::hasLocalSideEffect() const { /// - reference type [C++ [expr]] /// Expr::isLvalueResult Expr::isLvalue(ASTContext &Ctx) const { - // first, check the type (C99 6.3.2.1) - if (TR->isFunctionType()) // from isObjectType() + // first, check the type (C99 6.3.2.1). Expressions with function + // type in C are not lvalues, but they can be lvalues in C++. + if (!Ctx.getLangOptions().CPlusPlus && TR->isFunctionType()) return LV_NotObjectType; // Allow qualified void which is an incomplete type other than void (yuck). if (TR->isVoidType() && !Ctx.getCanonicalType(TR).getCVRQualifiers()) return LV_IncompleteVoidType; + /// FIXME: Expressions can't have reference type, so the following + /// isn't needed. if (TR->isReferenceType()) // C++ [expr] return LV_Valid; @@ -356,7 +360,11 @@ Expr::isLvalueResult Expr::isLvalue(ASTContext &Ctx) const { return LV_Valid; case DeclRefExprClass: { // C99 6.5.1p2 const Decl *RefdDecl = cast(this)->getDecl(); - if (isa(RefdDecl) || isa(RefdDecl)) + if (isa(RefdDecl) || + isa(RefdDecl) || + // C++ 3.10p2: An lvalue refers to an object or function. + isa(RefdDecl) || + isa(RefdDecl)) return LV_Valid; break; } diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 039571f9fe..d53e200ba9 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -259,7 +259,7 @@ public: QualType ConvertDeclSpecToType(const DeclSpec &DS); void ProcessTypeAttributeList(QualType &Result, const AttributeList *AL); QualType GetTypeForDeclarator(Declarator &D, Scope *S); - + QualType GetNonReferenceType(QualType Type); QualType ObjCGetTypeForMethodDefinition(DeclTy *D); @@ -371,6 +371,7 @@ private: bool IsFloatingPointPromotion(QualType FromType, QualType ToType); bool IsPointerConversion(Expr *From, QualType FromType, QualType ToType, QualType& ConvertedType); + bool IsQualificationConversion(QualType FromType, QualType ToType); ImplicitConversionSequence::CompareKind CompareImplicitConversionSequences(const ImplicitConversionSequence& ICS1, diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 7a3c6bdeb1..3ead4308d2 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -450,7 +450,7 @@ Sema::ExprResult Sema::ActOnIdentifierExpr(Scope *S, SourceLocation Loc, } // If this reference is not in a block or if the referenced variable is // within the block, create a normal DeclRefExpr. - return new DeclRefExpr(VD, VD->getType(), Loc); + return new DeclRefExpr(VD, GetNonReferenceType(VD->getType()), Loc); } Sema::ExprResult Sema::ActOnPredefinedExpr(SourceLocation Loc, @@ -1565,8 +1565,7 @@ Sema::CheckPointerTypesForAssignment(QualType lhsType, QualType rhsType) { // 3 & 4 (below). ...and the type *pointed to* by the left has all the // qualifiers of the type *pointed to* by the right; // FIXME: Handle ASQualType - if ((lhptee.getCVRQualifiers() & rhptee.getCVRQualifiers()) != - rhptee.getCVRQualifiers()) + if (!lhptee.isAtLeastAsQualifiedAs(rhptee)) ConvTy = CompatiblePointerDiscardsQualifiers; // C99 6.5.16.1p1 (constraint 4): If one operand is a pointer to an object or @@ -1766,6 +1765,28 @@ Sema::CheckAssignmentConstraints(QualType lhsType, QualType rhsType) { Sema::AssignConvertType Sema::CheckSingleAssignmentConstraints(QualType lhsType, Expr *&rExpr) { + if (getLangOptions().CPlusPlus) { + if (!lhsType->isRecordType()) { + // C++ 5.17p3: If the left operand is not of class type, the + // expression is implicitly converted (C++ 4) to the + // cv-unqualified type of the left operand. + ImplicitConversionSequence ICS + = TryCopyInitialization(rExpr, lhsType.getUnqualifiedType()); + if (ICS.ConversionKind == ImplicitConversionSequence::BadConversion) { + // No implicit conversion available; we cannot perform this + // assignment. + return Incompatible; + } else { + // Perform the appropriate cast to the right-handle side. + ImpCastExprToType(rExpr, lhsType.getUnqualifiedType()); + return Compatible; + } + } + + // FIXME: Currently, we fall through and treat C++ classes like C + // structures. + } + // C99 6.5.16.1p1: the left operand is a pointer and the right is // a null pointer constant. if ((lhsType->isPointerType() || lhsType->isObjCQualifiedIdType() || diff --git a/lib/Sema/SemaOverload.cpp b/lib/Sema/SemaOverload.cpp index 88c209b6e0..2696c98523 100644 --- a/lib/Sema/SemaOverload.cpp +++ b/lib/Sema/SemaOverload.cpp @@ -318,6 +318,32 @@ Sema::TryCopyInitialization(Expr* From, QualType ToType) ICS.Standard.Deprecated = false; ICS.Standard.FromTypePtr = FromType.getAsOpaquePtr(); + if (const ReferenceType *ToTypeRef = ToType->getAsReferenceType()) { + // FIXME: This is a hack to deal with the initialization of + // references the way that the C-centric code elsewhere deals with + // references, by only allowing them if the referred-to type is + // exactly the same. This means that we're only handling the + // direct-binding case. The code will be replaced by an + // implementation of C++ 13.3.3.1.4 once we have the + // initialization of references implemented. + QualType ToPointee = Context.getCanonicalType(ToTypeRef->getPointeeType()); + + // Get down to the canonical type that we're converting from. + if (const ReferenceType *FromTypeRef = FromType->getAsReferenceType()) + FromType = FromTypeRef->getPointeeType(); + FromType = Context.getCanonicalType(FromType); + + ICS.Standard.First = ICK_Identity; + ICS.Standard.Second = ICK_Identity; + ICS.Standard.Third = ICK_Identity; + ICS.Standard.ToTypePtr = ToType.getAsOpaquePtr(); + + if (FromType != ToPointee) + ICS.ConversionKind = ImplicitConversionSequence::BadConversion; + + return ICS; + } + // The first conversion can be an lvalue-to-rvalue conversion, // array-to-pointer conversion, or function-to-pointer conversion // (C++ 4p1). @@ -432,10 +458,7 @@ Sema::TryCopyInitialization(Expr* From, QualType ToType) } // The third conversion can be a qualification conversion (C++ 4p1). - // FIXME: CheckPointerTypesForAssignment isn't the right way to - // determine whether we have a qualification conversion. - if (Context.getCanonicalType(FromType) != Context.getCanonicalType(ToType) - && CheckPointerTypesForAssignment(ToType, FromType) == Compatible) { + if (IsQualificationConversion(FromType, ToType)) { ICS.Standard.Third = ICK_Qualification; FromType = ToType; } else { @@ -618,6 +641,74 @@ bool Sema::IsPointerConversion(Expr *From, QualType FromType, QualType ToType, return false; } +/// IsQualificationConversion - Determines whether the conversion from +/// an rvalue of type FromType to ToType is a qualification conversion +/// (C++ 4.4). +bool +Sema::IsQualificationConversion(QualType FromType, QualType ToType) +{ + FromType = Context.getCanonicalType(FromType); + ToType = Context.getCanonicalType(ToType); + + // If FromType and ToType are the same type, this is not a + // qualification conversion. + if (FromType == ToType) + return false; + + // (C++ 4.4p4): + // A conversion can add cv-qualifiers at levels other than the first + // in multi-level pointers, subject to the following rules: [...] + bool PreviousToQualsIncludeConst = true; + bool UnwrappedPointer; + bool UnwrappedAnyPointer = false; + do { + // Within each iteration of the loop, we check the qualifiers to + // determine if this still looks like a qualification + // conversion. Then, if all is well, we unwrap one more level of + // pointers (FIXME: or pointers-to-members) and do it all again + // until there are no more pointers or pointers-to-members left to + // unwrap. + UnwrappedPointer = false; + + // -- the pointer types are similar. + const PointerType *FromPtrType = FromType->getAsPointerType(), + *ToPtrType = ToType->getAsPointerType(); + if (FromPtrType && ToPtrType) { + // The pointer types appear similar. Look at their pointee types. + FromType = FromPtrType->getPointeeType(); + ToType = ToPtrType->getPointeeType(); + UnwrappedPointer = true; + UnwrappedAnyPointer = true; + } + + // FIXME: Cope with pointer-to-member types. + + // -- for every j > 0, if const is in cv 1,j then const is in cv + // 2,j, and similarly for volatile. + if (FromType.isMoreQualifiedThan(ToType)) + return false; + + // -- if the cv 1,j and cv 2,j are different, then const is in + // every cv for 0 < k < j. + if (FromType.getCVRQualifiers() != ToType.getCVRQualifiers() + && !PreviousToQualsIncludeConst) + return false; + + // Keep track of whether all prior cv-qualifiers in the "to" type + // include const. + PreviousToQualsIncludeConst + = PreviousToQualsIncludeConst && ToType.isConstQualified(); + } while (UnwrappedPointer); + + // We are left with FromType and ToType being the pointee types + // after unwrapping the original FromType and ToType the same number + // of types. If we unwrapped any pointers, and if FromType and + // ToType have the same unqualified type (since we checked + // qualifiers above), then this is a qualification conversion. + return UnwrappedAnyPointer && + FromType.getUnqualifiedType() == ToType.getUnqualifiedType(); +} + /// CompareImplicitConversionSequences - Compare two implicit /// conversion sequences to determine whether one is better than the /// other or if they are indistinguishable (C++ 13.3.3.2). diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index f1edabfa7e..942d7063a1 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -500,6 +500,23 @@ QualType Sema::GetTypeForDeclarator(Declarator &D, Scope *S) { return T; } +/// GetNonReferenceType - If Type is a reference type (e.g., const +/// int&), returns the type that the reference refers to ("const +/// int"). Otherwise, returns the type itself. This routine is used +/// throughout to implement C++ 5p6: +/// +/// If an expression initially has the type "reference to T" (8.3.2, +/// 8.5.3), the type is adjusted to "T" prior to any further +/// analysis, the expression designates the object or function +/// denoted by the reference, and the expression is an lvalue. +QualType Sema::GetNonReferenceType(QualType Type) +{ + if (const ReferenceType *RefType = Type->getAsReferenceType()) + return RefType->getPointeeType(); + else + return Type; +} + /// ObjCGetTypeForMethodDefinition - Builds the type for a method definition /// declarator QualType Sema::ObjCGetTypeForMethodDefinition(DeclTy *D) { diff --git a/test/SemaCXX/decl-expr-ambiguity.cpp b/test/SemaCXX/decl-expr-ambiguity.cpp index b2c3ea7b5a..5a76b93342 100644 --- a/test/SemaCXX/decl-expr-ambiguity.cpp +++ b/test/SemaCXX/decl-expr-ambiguity.cpp @@ -38,6 +38,6 @@ void fn(int(C)) { } // void fn(int(*fp)(C c)) { } int g(C); void foo() { - fn(1); // expected-error {{incompatible integer to pointer conversion passing 'int', expected 'int (*)(class C)'}} + fn(1); // expected-error {{incompatible type passing 'int', expected 'int (*)(class C)'}} fn(g); // OK } diff --git a/test/SemaCXX/qualification-conversion.cpp b/test/SemaCXX/qualification-conversion.cpp new file mode 100644 index 0000000000..d467d3101c --- /dev/null +++ b/test/SemaCXX/qualification-conversion.cpp @@ -0,0 +1,11 @@ +// RUN: clang -fsyntax-only -pedantic -verify %s +int* quals1(int const * p); +int* quals2(int const * const * pp); +int* quals3(int const * * const * ppp); + +void test_quals(int * p, int * * pp, int * * * ppp) { + int const * const * pp2 = pp; + quals1(p); + quals2(pp); + quals3(ppp); // expected-error {{ incompatible type passing 'int ***', expected 'int const **const *' }} +}