From: Douglas Gregor Date: Fri, 24 Oct 2008 04:54:22 +0000 (+0000) Subject: First non-embarrassing cut at checking for ambiguous derived-to-base X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=94b1dd2368dc9eeedf2794db654deae225fac763;p=clang First non-embarrassing cut at checking for ambiguous derived-to-base conversions. Added PerformImplicitConversion, which follows an implicit conversion sequence computed by TryCopyInitialization and actually performs the implicit conversions, including the extra check for ambiguity mentioned above. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@58071 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticKinds.def b/include/clang/Basic/DiagnosticKinds.def index f265f3ba26..f4e791ca91 100644 --- a/include/clang/Basic/DiagnosticKinds.def +++ b/include/clang/Basic/DiagnosticKinds.def @@ -1181,6 +1181,8 @@ DIAG(err_incomplete_base_class, ERROR, "base class has incomplete type") DIAG(err_duplicate_base_class, ERROR, "base class '%0' specified more than once as a direct base class") +DIAG(err_ambiguous_derived_to_base_conv, ERROR, + "ambiguous conversion from derived class '%0' to base class '%1':%2") DIAG(warn_not_compound_assign, WARNING, "use of unary operator that may be intended as compound assignment (%0=)") diff --git a/lib/Driver/TextDiagnosticBuffer.cpp b/lib/Driver/TextDiagnosticBuffer.cpp index 1df93750f0..26ac879dd0 100644 --- a/lib/Driver/TextDiagnosticBuffer.cpp +++ b/lib/Driver/TextDiagnosticBuffer.cpp @@ -14,7 +14,8 @@ #include "clang/Driver/TextDiagnosticBuffer.h" using namespace clang; -/// HandleDiagnostic - Store the errors & warnings that are reported. +/// HandleDiagnostic - Store the errors, warnings, and notes that are +/// reported. /// void TextDiagnosticBuffer::HandleDiagnostic(Diagnostic &Diags, Diagnostic::Level Level, diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 5a5c99c700..ba07a9c174 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -23,8 +23,8 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/OwningPtr.h" -#include #include +#include namespace llvm { class APSInt; @@ -66,6 +66,7 @@ namespace clang { class ObjCMethodDecl; class ObjCPropertyDecl; struct BlockSemaInfo; + class BasePaths; /// PragmaPackStack - Simple class to wrap the stack used by #pragma /// pack. @@ -371,6 +372,7 @@ private: bool IsFloatingPointPromotion(QualType FromType, QualType ToType); bool IsPointerConversion(Expr *From, QualType FromType, QualType ToType, QualType& ConvertedType); + bool CheckPointerConversion(Expr *From, QualType ToType); bool IsQualificationConversion(QualType FromType, QualType ToType); ImplicitConversionSequence::CompareKind @@ -785,6 +787,10 @@ public: unsigned NumBases); bool IsDerivedFrom(QualType Derived, QualType Base); + bool IsDerivedFrom(QualType Derived, QualType Base, BasePaths &Paths); + + bool CheckDerivedToBaseConversion(SourceLocation Loc, SourceRange Range, + QualType Derived, QualType Base); // Objective-C declarations. virtual DeclTy *ActOnStartClassInterface(SourceLocation AtInterfaceLoc, @@ -1028,6 +1034,10 @@ private: QualType rhsType); bool IsStringLiteralToNonConstPointerConversion(Expr *From, QualType ToType); + + bool PerformImplicitConversion(Expr *&From, QualType ToType); + bool PerformImplicitConversion(Expr *&From, QualType ToType, + const StandardConversionSequence& SCS); /// the following "Check" methods will return a valid/converted QualType /// or a null QualType (indicating an error diagnostic was issued). diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 9f27f8d7cd..ecc8e240c7 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -1774,17 +1774,10 @@ Sema::CheckSingleAssignmentConstraints(QualType lhsType, Expr *&rExpr) { // 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. + if (PerformImplicitConversion(rExpr, lhsType.getUnqualifiedType())) return Incompatible; - } else { - // Perform the appropriate cast to the right-handle side. - ImpCastExprToType(rExpr, lhsType.getUnqualifiedType()); + else return Compatible; - } } // FIXME: Currently, we fall through and treat C++ classes like C diff --git a/lib/Sema/SemaExprCXX.cpp b/lib/Sema/SemaExprCXX.cpp index 540d92dbae..c206e1a9cd 100644 --- a/lib/Sema/SemaExprCXX.cpp +++ b/lib/Sema/SemaExprCXX.cpp @@ -235,3 +235,128 @@ Sema::IsStringLiteralToNonConstPointerConversion(Expr *From, QualType ToType) { return false; } + +/// PerformImplicitConversion - Perform an implicit conversion of the +/// expression From to the type ToType. Returns true if there was an +/// error, false otherwise. The expression From is replaced with the +/// converted expression. +bool +Sema::PerformImplicitConversion(Expr *&From, QualType ToType) +{ + ImplicitConversionSequence ICS = TryCopyInitialization(From, ToType); + switch (ICS.ConversionKind) { + case ImplicitConversionSequence::StandardConversion: + if (PerformImplicitConversion(From, ToType, ICS.Standard)) + return true; + break; + + case ImplicitConversionSequence::UserDefinedConversion: + // FIXME: This is, of course, wrong. We'll need to actually call + // the constructor or conversion operator, and then cope with the + // standard conversions. + ImpCastExprToType(From, ToType); + break; + + case ImplicitConversionSequence::EllipsisConversion: + assert(false && "Cannot perform an ellipsis conversion"); + break; + + case ImplicitConversionSequence::BadConversion: + return true; + } + + // Everything went well. + return false; +} + +/// PerformImplicitConversion - Perform an implicit conversion of the +/// expression From to the type ToType by following the standard +/// conversion sequence SCS. Returns true if there was an error, false +/// otherwise. The expression From is replaced with the converted +/// expression. +bool +Sema::PerformImplicitConversion(Expr *&From, QualType ToType, + const StandardConversionSequence& SCS) +{ + // Overall FIXME: we are recomputing too many types here and doing + // far too much extra work. What this means is that we need to keep + // track of more information that is computed when we try the + // implicit conversion initially, so that we don't need to recompute + // anything here. + QualType FromType = From->getType(); + + // Perform the first implicit conversion. + switch (SCS.First) { + case ICK_Identity: + case ICK_Lvalue_To_Rvalue: + // Nothing to do. + break; + + case ICK_Array_To_Pointer: + FromType = Context.getArrayDecayedType(FromType); + ImpCastExprToType(From, FromType); + break; + + case ICK_Function_To_Pointer: + FromType = Context.getPointerType(FromType); + ImpCastExprToType(From, FromType); + break; + + default: + assert(false && "Improper first standard conversion"); + break; + } + + // Perform the second implicit conversion + switch (SCS.Second) { + case ICK_Identity: + // Nothing to do. + break; + + case ICK_Integral_Promotion: + case ICK_Floating_Promotion: + case ICK_Integral_Conversion: + case ICK_Floating_Conversion: + case ICK_Floating_Integral: + FromType = ToType.getUnqualifiedType(); + ImpCastExprToType(From, FromType); + break; + + case ICK_Pointer_Conversion: + if (CheckPointerConversion(From, ToType)) + return true; + ImpCastExprToType(From, ToType); + break; + + case ICK_Pointer_Member: + // FIXME: Implement pointer-to-member conversions. + assert(false && "Pointer-to-member conversions are unsupported"); + break; + + case ICK_Boolean_Conversion: + FromType = Context.BoolTy; + ImpCastExprToType(From, FromType); + break; + + default: + assert(false && "Improper second standard conversion"); + break; + } + + switch (SCS.Third) { + case ICK_Identity: + // Nothing to do. + break; + + case ICK_Qualification: + ImpCastExprToType(From, ToType); + break; + + default: + assert(false && "Improper second standard conversion"); + break; + } + + return false; +} + diff --git a/lib/Sema/SemaInherit.cpp b/lib/Sema/SemaInherit.cpp index 6890dbfc64..256ee24e1c 100644 --- a/lib/Sema/SemaInherit.cpp +++ b/lib/Sema/SemaInherit.cpp @@ -14,19 +14,56 @@ //===----------------------------------------------------------------------===// #include "Sema.h" +#include "SemaInherit.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/Type.h" +#include "clang/AST/TypeOrdering.h" +#include "clang/Basic/Diagnostic.h" +#include +#include +#include namespace clang { +/// isAmbiguous - Determines whether the set of paths provided is +/// ambiguous, i.e., there are two or more paths that refer to +/// different base class subobjects of the same type. BaseType must be +/// an unqualified, canonical class type. +bool BasePaths::isAmbiguous(QualType BaseType) { + std::pair& Subobjects = ClassSubobjects[BaseType]; + return Subobjects.second + (Subobjects.first? 1 : 0) > 1; +} + +/// clear - Clear out all prior path information. +void BasePaths::clear() { + Paths.clear(); + ClassSubobjects.clear(); + ScratchPath.clear(); +} + +/// IsDerivedFrom - Determine whether the class type Derived is +/// derived from the class type Base, ignoring qualifiers on Base and +/// Derived. This routine does not assess whether an actual conversion +/// from a Derived* to a Base* is legal, because it does not account +/// for ambiguous conversions or conversions to private/protected bases. +bool Sema::IsDerivedFrom(QualType Derived, QualType Base) { + BasePaths Paths(/*FindAmbiguities=*/false, /*RecordPaths=*/false); + return IsDerivedFrom(Derived, Base, Paths); +} + /// IsDerivedFrom - Determine whether the class type Derived is /// derived from the class type Base, ignoring qualifiers on Base and /// Derived. This routine does not assess whether an actual conversion /// from a Derived* to a Base* is legal, because it does not account /// for ambiguous conversions or conversions to private/protected -/// bases. -bool Sema::IsDerivedFrom(QualType Derived, QualType Base) -{ +/// bases. This routine will use Paths to determine if there are +/// ambiguous paths (if @c Paths.isFindingAmbiguities()) and record +/// information about all of the paths (if +/// @c Paths.isRecordingPaths()). +bool Sema::IsDerivedFrom(QualType Derived, QualType Base, BasePaths &Paths) { + bool FoundPath = false; + Derived = Context.getCanonicalType(Derived).getUnqualifiedType(); Base = Context.getCanonicalType(Base).getUnqualifiedType(); @@ -41,12 +78,120 @@ bool Sema::IsDerivedFrom(QualType Derived, QualType Base) = static_cast(DerivedType->getDecl()); for (CXXRecordDecl::base_class_const_iterator BaseSpec = Decl->bases_begin(); BaseSpec != Decl->bases_end(); ++BaseSpec) { - if (Context.getCanonicalType(BaseSpec->getType()) == Base - || IsDerivedFrom(BaseSpec->getType(), Base)) - return true; + // Find the record of the base class subobjects for this type. + QualType BaseType = Context.getCanonicalType(BaseSpec->getType()); + BaseType = BaseType.getUnqualifiedType(); + + // Determine whether we need to visit this base class at all, + // updating the count of subobjects appropriately. + std::pair& Subobjects = Paths.ClassSubobjects[BaseType]; + bool VisitBase = true; + if (BaseSpec->isVirtual()) { + VisitBase = !Subobjects.first; + Subobjects.first = true; + } else + ++Subobjects.second; + + if (Paths.isRecordingPaths()) { + // Add this base specifier to the current path. + BasePathElement Element; + Element.Base = &*BaseSpec; + if (BaseSpec->isVirtual()) + Element.SubobjectNumber = 0; + else + Element.SubobjectNumber = Subobjects.second; + Paths.ScratchPath.push_back(Element); + } + + if (Context.getCanonicalType(BaseSpec->getType()) == Base) { + // We've found the base we're looking for. + FoundPath = true; + if (Paths.isRecordingPaths()) { + // We have a path. Make a copy of it before moving on. + Paths.Paths.push_back(Paths.ScratchPath); + } else if (!Paths.isFindingAmbiguities()) { + // We found a path and we don't care about ambiguities; + // return immediately. + return FoundPath; + } + } else if (VisitBase && IsDerivedFrom(BaseSpec->getType(), Base, Paths)) { + // There is a path to the base we want. If we're not + // collecting paths or finding ambiguities, we're done. + FoundPath = true; + if (!Paths.isFindingAmbiguities()) + return FoundPath; + } + + // Pop this base specifier off the current path (if we're + // collecting paths). + if (Paths.isRecordingPaths()) + Paths.ScratchPath.pop_back(); } } + return FoundPath; +} + +/// CheckDerivedToBaseConversion - Check whether the Derived-to-Base +/// conversion (where Derived and Base are class types) is +/// well-formed, meaning that the conversion is unambiguous (and +/// FIXME: that all of the base classes are accessible). Returns true +/// and emits a diagnostic if the code is ill-formed, returns false +/// otherwise. Loc is the location where this routine should point to +/// if there is an error, and Range is the source range to highlight +/// if there is an error. +bool +Sema::CheckDerivedToBaseConversion(SourceLocation Loc, SourceRange Range, + QualType Derived, QualType Base) { + // First, determine whether the path from Derived to Base is + // ambiguous. This is slightly more expensive than checking whether + // the Derived to Base conversion exists, because here we need to + // explore multiple paths to determine if there is an ambiguity. + BasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/false); + bool DerivationOkay = IsDerivedFrom(Derived, Base, Paths); + assert(DerivationOkay && "Can only be used with a derived-to-base conversion"); + if (!DerivationOkay) + return true; + + if (Paths.isAmbiguous(Context.getCanonicalType(Base).getUnqualifiedType())) { + // We know that the derived-to-base conversion is + // ambiguous. Perform the derived-to-base search just one more + // time to compute all of the possible paths so that we can print + // them out. This is more expensive than any of the previous + // derived-to-base checks we've done, but at this point we know + // we'll be issuing a diagnostic so performance isn't as much of + // an issue. + Paths.clear(); + Paths.setRecordingPaths(true); + bool StillOkay = IsDerivedFrom(Derived, Base, Paths); + assert(StillOkay && "Can only be used with a derived-to-base conversion"); + if (!StillOkay) + return true; + + // Build up a textual representation of the ambiguous paths, e.g., + // D -> B -> A, that will be used to illustrate the ambiguous + // conversions in the diagnostic. We only print one of the paths + // to each base class subobject. + std::string PathDisplayStr; + std::set DisplayedPaths; + for (BasePaths::paths_iterator Path = Paths.begin(); + Path != Paths.end(); ++Path) { + if (DisplayedPaths.insert(Path->back().SubobjectNumber).second) { + // We haven't displayed a path to this particular base + // class subobject yet. + PathDisplayStr += "\n "; + PathDisplayStr += Derived.getAsString(); + for (BasePath::const_iterator Element = Path->begin(); + Element != Path->end(); ++Element) + PathDisplayStr += " -> " + Element->Base->getType().getAsString(); + } + } + + Diag(Loc, diag::err_ambiguous_derived_to_base_conv, + Derived.getAsString(), Base.getAsString(), PathDisplayStr, Range); + return true; + } + return false; } diff --git a/lib/Sema/SemaInherit.h b/lib/Sema/SemaInherit.h new file mode 100644 index 0000000000..801ed7442d --- /dev/null +++ b/lib/Sema/SemaInherit.h @@ -0,0 +1,144 @@ +//===------ SemaInherit.h - C++ Inheritance ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file provides Sema data structures that help analyse C++ +// inheritance semantics, including searching the inheritance +// hierarchy. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SEMA_INHERIT_H +#define LLVM_CLANG_SEMA_INHERIT_H + +#include "clang/AST/Type.h" +#include "clang/AST/TypeOrdering.h" +#include "llvm/ADT/SmallVector.h" +#include +#include + +namespace clang { + class Sema; + class CXXBaseSpecifier; + + /// BasePathElement - An element in a path from a derived class to a + /// base class. Each step in the path references the link from a + /// derived class to one of its direct base classes, along with a + /// base "number" that identifies which base subobject of the + /// original derived class we are referencing. + struct BasePathElement { + /// Base - The base specifier that states the link from a derived + /// class to a base class, which will be followed by this base + /// path element. + const CXXBaseSpecifier *Base; + + /// SubobjectNumber - Identifies which base class subobject (of type + /// @c Base->getType()) this base path element refers to. This + /// value is only valid if @c !Base->isVirtual(), because there + /// is no base numbering for the zero or one virtual bases of a + /// given type. + int SubobjectNumber; + }; + + /// BasePath - Represents a path from a specific derived class + /// (which is not represented as part of the path) to a particular + /// (direct or indirect) base class subobject. Individual elements + /// in the path are described by the BasePathElement structure, + /// which captures both the link from a derived class to one of its + /// direct bases and identification describing which base class + /// subobject is being used. + typedef llvm::SmallVector BasePath; + + /// BasePaths - Represents the set of paths from a derived class to + /// one of its (direct or indirect) bases. For example, given the + /// following class hierachy: + /// + /// @code + /// class A { }; + /// class B : public A { }; + /// class C : public A { }; + /// class D : public B, public C{ }; + /// @endcode + /// + /// There are two potential BasePaths to represent paths from D to a + /// base subobject of type A. One path is (D,0) -> (B,0) -> (A,0) + /// and another is (D,0)->(C,0)->(A,1). These two paths actually + /// refer to two different base class subobjects of the same type, + /// so the BasePaths object refers to an ambiguous path. On the + /// other hand, consider the following class hierarchy: + /// + /// @code + /// class A { }; + /// class B : public virtual A { }; + /// class C : public virtual A { }; + /// class D : public B, public C{ }; + /// @endcode + /// + /// Here, there are two potential BasePaths again, (D, 0) -> (B, 0) + /// -> (A,v) and (D, 0) -> (C, 0) -> (A, v), but since both of them + /// refer to the same base class subobject of type A (the virtual + /// one), there is no ambiguity. + class BasePaths { + /// Paths - The actual set of paths that can be taken from the + /// derived class to the same base class. + std::list Paths; + + /// ClassSubobjects - Records the class subobjects for each class + /// type that we've seen. The first element in the pair says + /// whether we found a path to a virtual base for that class type, + /// while the element contains the number of non-virtual base + /// class subobjects for that class type. The key of the map is + /// the cv-unqualified canonical type of the base class subobject. + std::map, QualTypeOrdering> + ClassSubobjects; + + /// FindAmbiguities - Whether Sema::IsDirectedFrom should try find + /// ambiguous paths while it is looking for a path from a derived + /// type to a base type. + bool FindAmbiguities; + + /// RecordPaths - Whether Sema::IsDirectedFrom should record paths + /// while it is determining whether there are paths from a derived + /// type to a base type. + bool RecordPaths; + + /// ScratchPath - A BasePath that is used by Sema::IsDerivedFrom + /// to help build the set of paths. + BasePath ScratchPath; + + friend class Sema; + + public: + typedef std::list::const_iterator paths_iterator; + + /// BasePaths - Construct a new BasePaths structure to record the + /// paths for a derived-to-base search. + explicit BasePaths(bool FindAmbiguities = true, bool RecordPaths = true) + : FindAmbiguities(FindAmbiguities), RecordPaths(RecordPaths) { } + + paths_iterator begin() const { return Paths.begin(); } + paths_iterator end() const { return Paths.end(); } + + bool isAmbiguous(QualType BaseType); + + /// isFindingAmbiguities - Whether we are finding multiple paths + /// to detect ambiguities. + bool isFindingAmbiguities() const { return FindAmbiguities; } + + /// isRecordingPaths - Whether we are recording paths. + bool isRecordingPaths() const { return RecordPaths; } + + /// setRecordingPaths - Specify whether we should be recording + /// paths or not. + void setRecordingPaths(bool RP) { RecordPaths = RP; } + + void clear(); + }; +} + +#endif diff --git a/lib/Sema/SemaOverload.cpp b/lib/Sema/SemaOverload.cpp index c967b04335..11b9e66576 100644 --- a/lib/Sema/SemaOverload.cpp +++ b/lib/Sema/SemaOverload.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "Sema.h" +#include "SemaInherit.h" #include "clang/Basic/Diagnostic.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" @@ -670,7 +671,8 @@ bool Sema::IsPointerConversion(Expr *From, QualType FromType, QualType ToType, // derived class object. The null pointer value is converted to // the null pointer value of the destination type. // - // Note that we do not check for ambiguity or inaccessibility here. + // Note that we do not check for ambiguity or inaccessibility + // here. That is handled by CheckPointerConversion. if (const PointerType *FromPtrType = FromType->getAsPointerType()) if (const PointerType *ToPtrType = ToType->getAsPointerType()) { if (FromPtrType->getPointeeType()->isRecordType() && @@ -701,6 +703,33 @@ bool Sema::IsPointerConversion(Expr *From, QualType FromType, QualType ToType, return false; } +/// CheckPointerConversion - Check the pointer conversion from the +/// expression From to the type ToType. This routine checks for +/// ambiguous (FIXME: or inaccessible) derived-to-base pointer +/// conversions for which IsPointerConversion has already returned +/// true. It returns true and produces a diagnostic if there was an +/// error, or returns false otherwise. +bool Sema::CheckPointerConversion(Expr *From, QualType ToType) { + QualType FromType = From->getType(); + + if (const PointerType *FromPtrType = FromType->getAsPointerType()) + if (const PointerType *ToPtrType = ToType->getAsPointerType()) { + BasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/false); + QualType FromPointeeType = FromPtrType->getPointeeType(), + ToPointeeType = ToPtrType->getPointeeType(); + if (FromPointeeType->isRecordType() && + ToPointeeType->isRecordType()) { + // We must have a derived-to-base conversion. Check an + // ambiguous or inaccessible conversion. + return CheckDerivedToBaseConversion(From->getExprLoc(), + From->getSourceRange(), + FromPointeeType, ToPointeeType); + } + } + + return false; +} + /// IsQualificationConversion - Determines whether the conversion from /// an rvalue of type FromType to ToType is a qualification conversion /// (C++ 4.4). diff --git a/test/SemaCXX/derived-to-base-ambig.cpp b/test/SemaCXX/derived-to-base-ambig.cpp new file mode 100644 index 0000000000..fa9fff48a9 --- /dev/null +++ b/test/SemaCXX/derived-to-base-ambig.cpp @@ -0,0 +1,33 @@ +// RUN: clang -fsyntax-only -verify %s +class A { }; +class B : public A { }; +class C : public A { }; +class D : public B, public C { }; + +void f(D* d) { + A* a; + a = d; // expected-error{{ambiguous conversion from derived class 'class D' to base class 'class A'}} expected-error{{incompatible type assigning 'class D *', expected 'class A *'}} +} + +class Object2 { }; +class A2 : public Object2 { }; +class B2 : public virtual A2 { }; +class C2 : virtual public A2 { }; +class D2 : public B2, public C2 { }; +class E2 : public D2, public C2, public virtual A2 { }; +class F2 : public E2, public A2 { }; + +void g(E2* e2, F2* f2) { + Object2* o2; + o2 = e2; + o2 = f2; // expected-error{{ambiguous conversion from derived class 'class F2' to base class 'class Object2'}} expected-error{{incompatible type assigning 'class F2 *', expected 'class Object2 *'}} +} + +// Test that ambiguous/inaccessibility checking does not trigger too +// early, because it should not apply during overload resolution. +void overload_okay(Object2*); +void overload_okay(E2*); + +void overload_call(F2* f2) { + overload_okay(f2); +}