Rudimentary 'auto' type deduction implementation

Hi Everyone,

Way back in August, I had a rudimentary implementation of the C++0x type deduction via the ‘auto’ type. I never got around to adding the appropriate tests and then there was a major refactor of much of the Sema code that I think made my implementation seem out of place. I’ve finally gotten around to looking at this again and would like the advice of those more experienced in the type system.

Also, I wasn’t sure if I should attach my diff as a file or embed it inline, so I’ve done both.

Thanks ahead of time for your help!

Index: include/clang/Sema/Sema.h

auto_type.diff (7.92 KB)

Hi Everyone,

Way back in August, I had a rudimentary implementation of the C++0x type deduction via the ‘auto’ type. I never got around to adding the appropriate tests and then there was a major refactor of much of the Sema code that I think made my implementation seem out of place. I’ve finally gotten around to looking at this again and would like the advice of those more experienced in the type system.

Also, I wasn’t sure if I should attach my diff as a file or embed it inline, so I’ve done both.

Thanks ahead of time for your help!

Index: include/clang/Sema/Sema.h

— include/clang/Sema/Sema.h (revision 121900)
+++ include/clang/Sema/Sema.h (working copy)
@@ -986,6 +986,13 @@
bool IsOverload(FunctionDecl *New, FunctionDecl *Old, bool IsForUsingDecl);

bool TryImplicitConversion(InitializationSequence &Sequence,

  • QualType ToType,
  • Expr *From,
  • bool SuppressUserConversions,
  • bool AllowExplicit,
  • bool InOverloadResolution);
  • bool TryImplicitConversion(InitializationSequence &Sequence,
    const InitializedEntity &Entity,
    Expr *From,
    bool SuppressUserConversions,
    Index: include/clang/Sema/Initialization.h
    ===================================================================
    — include/clang/Sema/Initialization.h (revision 121900)
    +++ include/clang/Sema/Initialization.h (working copy)
    @@ -552,6 +552,8 @@

/// \brief Steps taken by this initialization.
llvm::SmallVector<Step, 4> Steps;
+

  • QualType DeducedType;

public:
/// \brief Describes why initialization failed.
Index: include/clang/AST/CanonicalType.h

— include/clang/AST/CanonicalType.h (revision 121900)
+++ include/clang/AST/CanonicalType.h (working copy)
@@ -277,6 +277,7 @@
LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(bool, isUnionType)
LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(bool, isComplexIntegerType)
LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(bool, isNullPtrType)

  • LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(bool, isUndeducedAutoType)
    LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(bool, isDependentType)
    LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(bool, isOverloadableType)
    LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(bool, isArrayType)
    Index: include/clang/AST/Type.h
    ===================================================================
    — include/clang/AST/Type.h (revision 121900)
    +++ include/clang/AST/Type.h (working copy)
    @@ -1176,6 +1176,7 @@
    bool isObjCBuiltinType() const; // ‘id’ or ‘Class’
    bool isTemplateTypeParmType() const; // C++ template type parameter
    bool isNullPtrType() const; // C++0x nullptr_t
  • bool isUndeducedAutoType() const; // C++0x yet to be deduced ‘auto’

enum ScalarTypeKind {
STK_Pointer,
Index: lib/Sema/SemaInit.cpp

— lib/Sema/SemaInit.cpp (revision 121900)
+++ lib/Sema/SemaInit.cpp (working copy)
@@ -3094,12 +3094,6 @@
// parenthesized list of expressions.
QualType DestType = Entity.getType();

  • if (DestType->isDependentType() ||
  • Expr::hasAnyTypeDependentArguments(Args, NumArgs)) {
  • SequenceKind = DependentSequence;
  • return;
  • }

for (unsigned I = 0; I != NumArgs; ++I)
if (Args[I]->getObjectKind() == OK_ObjCProperty)
S.ConvertPropertyForRValue(Args[I]);
@@ -3111,6 +3105,19 @@
if (!isa(Initializer))
SourceType = Initializer->getType();
}
+

  • if (S.getLangOptions().CPlusPlus0x) {
  • if (DestType->isUndeducedAutoType() && !SourceType.isNull()) { // TODO: Can ‘auto’ be initialized with a list?
  • DeducedType = SourceType;
  • DestType = SourceType;
  • }
  • }

There’s more to it than just copying the deduced type over. For one, there may not be a source type at this point, e.g., if we are attempting to direct-initialize with multiple arguments. Also, there’s some matching that needs to be done.

int x;
auto *y = x; // should be an error

The process is like a restricted form of template argument deduction, but much simpler.

  • if (DestType->isDependentType() ||
  • Expr::hasAnyTypeDependentArguments(Args, NumArgs)) {
  • SequenceKind = DependentSequence;
  • return;
  • }

Are we sure that it’s safe to move this check down here? For example, did you verify that ConvertPropertyForRValue does the right thing for type-dependent expressions?

// - If the initializer is a braced-init-list, the object is
// list-initialized (8.5.4).
@@ -3183,7 +3190,7 @@
(Context.hasSameUnqualifiedType(SourceType, DestType) ||
S.IsDerivedFrom(SourceType, DestType))))
TryConstructorInitialization(S, Entity, Kind, Args, NumArgs,

  • Entity.getType(), *this);
  • DestType, *this);
    // - Otherwise (i.e., for the remaining copy-initialization cases),
    // user-defined conversion sequences that can convert from the source
    // type to the destination type or (when a conversion function is
    @@ -3213,7 +3220,7 @@
    // conversions (Clause 4) will be used, if necessary, to convert the
    // initializer expression to the cv-unqualified version of the
    // destination type; no user-defined conversions are considered.
  • if (S.TryImplicitConversion(*this, Entity, Initializer,
  • if (S.TryImplicitConversion(*this, DestType, Initializer,
    /SuppressUserConversions/ true,
    /AllowExplicitConversions/ false,
    /InOverloadResolution/ false))
    @@ -3548,6 +3555,10 @@
    return ExprError();
    }

  • if (ResultType && !DeducedType.isNull()) {

  • *ResultType = DeducedType;

  • }

if (SequenceKind == DependentSequence) {
// If the declaration is a non-dependent, incomplete array type
// that has an initializer, then its type will be completed once
@@ -3609,7 +3620,7 @@
// FIXME: Ugly hack around the fact that Entity.getType() is not
// the same as Entity.getDecl()->getType() in cases involving type merging,
// and we want latter when it makes sense.

  • if (ResultType)
  • if (ResultType && DeducedType.isNull())
    *ResultType = Entity.getDecl() ? Entity.getDecl()->getType() :
    Entity.getType();

Index: lib/Sema/SemaOverload.cpp

— lib/Sema/SemaOverload.cpp (revision 121900)
+++ lib/Sema/SemaOverload.cpp (working copy)
@@ -834,23 +834,37 @@
}

bool Sema::TryImplicitConversion(InitializationSequence &Sequence,

  • const InitializedEntity &Entity,
  • QualType ToType,
    Expr *Initializer,
    bool SuppressUserConversions,
    bool AllowExplicitConversions,
    bool InOverloadResolution) {
    ImplicitConversionSequence ICS
  • = clang::TryImplicitConversion(*this, Initializer, Entity.getType(),
  • = clang::TryImplicitConversion(*this, Initializer, ToType,
    SuppressUserConversions,
    AllowExplicitConversions,
    InOverloadResolution);
    if (ICS.isBad()) return true;

// Perform the actual conversion.

  • Sequence.AddConversionSequenceStep(ICS, Entity.getType());
  • Sequence.AddConversionSequenceStep(ICS, ToType);
    return false;
    }

+bool Sema::TryImplicitConversion(InitializationSequence &Sequence,

  • const InitializedEntity &Entity,
  • Expr *Initializer,
  • bool SuppressUserConversions,
  • bool AllowExplicitConversions,
  • bool InOverloadResolution) {
  • return TryImplicitConversion(Sequence, Entity.getType(),
  • Initializer, SuppressUserConversions,
  • AllowExplicitConversions, InOverloadResolution);
    +}

/// 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
Index: lib/AST/Type.cpp

— lib/AST/Type.cpp (revision 121900)
+++ lib/AST/Type.cpp (working copy)
@@ -862,6 +862,12 @@
return false;
}

+bool Type::isUndeducedAutoType() const {

  • if (const BuiltinType *BT = getAs())
  • return BT->getKind() == BuiltinType::UndeducedAuto;
  • return false;
    +}

Would this also work for reference-to-auto and pointer-to-auto cases?

There’s one big thing missing here that I’d like to see addressed in any implementation of “auto”: the AST should represent the fact that (1) auto was used in the declaration, and (2) what type was deduced for the “auto” keyword itself. For example, given

int x;
auto *xp = &x;

we want to know that the “auto” was deduced to “int”.

  • Doug