[cfe-commits] r78278 - in /cfe/trunk: include/clang/Parse/Action.h lib/Frontend/PrintParserCallbacks.cpp lib/Parse/ParseExpr.cpp lib/Sema/Sema.h lib/Sema/SemaCXXScopeSpec.cpp lib/Sema/SemaExpr.cpp lib/Sema/SemaOverload.cpp lib/Sema/SemaTemplateInstantiateExpr.cpp test/SemaCXX/qual-id-test.cpp

Douglas Gregor dgregor at apple.com
Wed Aug 5 20:17:16 PDT 2009


Author: dgregor
Date: Wed Aug  5 22:17:00 2009
New Revision: 78278

URL: http://llvm.org/viewvc/llvm-project?rev=78278&view=rev
Log:
Support nested-name-specifiers for C++ member access expressions, e.g.,

  this->Base::foo

from James Porter!

Added:
    cfe/trunk/test/SemaCXX/qual-id-test.cpp   (with props)
Modified:
    cfe/trunk/include/clang/Parse/Action.h
    cfe/trunk/lib/Frontend/PrintParserCallbacks.cpp
    cfe/trunk/lib/Parse/ParseExpr.cpp
    cfe/trunk/lib/Sema/Sema.h
    cfe/trunk/lib/Sema/SemaCXXScopeSpec.cpp
    cfe/trunk/lib/Sema/SemaExpr.cpp
    cfe/trunk/lib/Sema/SemaOverload.cpp
    cfe/trunk/lib/Sema/SemaTemplateInstantiateExpr.cpp

Modified: cfe/trunk/include/clang/Parse/Action.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Parse/Action.h?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/include/clang/Parse/Action.h (original)
+++ cfe/trunk/include/clang/Parse/Action.h Wed Aug  5 22:17:00 2009
@@ -236,6 +236,27 @@
     return 0; 
   }
 
+  /// ActOnCXXEnterMemberScope - Called when a C++ class member accessor ('.'
+  /// or '->') is parsed. After this method is called, according to
+  /// [C++ 3.4.5p4], qualified-ids should be looked up in the contexts of both
+  /// the entire postfix-expression and the scope of the class of the object
+  /// expression.
+  /// 'SS' should be an empty CXXScopeSpec to be filled with the class's scope.
+  virtual OwningExprResult ActOnCXXEnterMemberScope(Scope *S,
+                                                    CXXScopeSpec &SS,
+                                                    ExprArg Base,
+                                                    tok::TokenKind OpKind) {
+    return ExprEmpty();
+  }
+
+  /// ActOnCXXExitMemberScope - Called when a postfix-expression that previously
+  /// invoked ActOnCXXEnterMemberScope() is finished. 'SS' is the same
+  /// CXXScopeSpec that was passed to ActOnCXXEnterMemberScope. Used to
+  /// indicate that names should revert to being looked up in the defining
+  /// scope.
+  virtual void ActOnCXXExitMemberScope(Scope *S, const CXXScopeSpec &SS) {
+  }
+
   /// ActOnCXXEnterDeclaratorScope - Called when a C++ scope specifier (global
   /// scope or nested-name-specifier) is parsed, part of a declarator-id.
   /// After this method is called, according to [C++ 3.4.3p3], names should be
@@ -820,7 +841,8 @@
                                                     tok::TokenKind OpKind,
                                                     SourceLocation MemberLoc,
                                                     IdentifierInfo &Member,
-                                                    DeclPtrTy ObjCImpDecl) {
+                                                    DeclPtrTy ObjCImpDecl,
+                                                const CXXScopeSpec *SS = 0) {
     return ExprEmpty();
   }
 

Modified: cfe/trunk/lib/Frontend/PrintParserCallbacks.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Frontend/PrintParserCallbacks.cpp?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/lib/Frontend/PrintParserCallbacks.cpp (original)
+++ cfe/trunk/lib/Frontend/PrintParserCallbacks.cpp Wed Aug  5 22:17:00 2009
@@ -532,7 +532,8 @@
                                                       tok::TokenKind OpKind,
                                                       SourceLocation MemberLoc,
                                                       IdentifierInfo &Member,
-                                                      DeclPtrTy ImplDecl) {
+                                                      DeclPtrTy ImplDecl,
+                                                      const CXXScopeSpec *SS=0) {
       Out << __FUNCTION__ << "\n";
       return ExprEmpty();
     }

Modified: cfe/trunk/lib/Parse/ParseExpr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Parse/ParseExpr.cpp?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/lib/Parse/ParseExpr.cpp (original)
+++ cfe/trunk/lib/Parse/ParseExpr.cpp Wed Aug  5 22:17:00 2009
@@ -919,6 +919,16 @@
       tok::TokenKind OpKind = Tok.getKind();
       SourceLocation OpLoc = ConsumeToken();  // Eat the "." or "->" token.
 
+      CXXScopeSpec MemberSS;
+      CXXScopeSpec SS;
+      if (getLang().CPlusPlus && !LHS.isInvalid()) {
+        LHS = Actions.ActOnCXXEnterMemberScope(CurScope, MemberSS, move(LHS),
+                                               OpKind);
+        if (LHS.isInvalid())
+          break;
+        ParseOptionalCXXScopeSpecifier(SS);
+      }
+
       if (Tok.isNot(tok::identifier)) {
         Diag(Tok, diag::err_expected_ident);
         return ExprError();
@@ -928,8 +938,12 @@
         LHS = Actions.ActOnMemberReferenceExpr(CurScope, move(LHS), OpLoc,
                                                OpKind, Tok.getLocation(),
                                                *Tok.getIdentifierInfo(),
-                                               ObjCImpDecl);
+                                               ObjCImpDecl, &SS);
       }
+
+      if (getLang().CPlusPlus)
+        Actions.ActOnCXXExitMemberScope(CurScope, MemberSS);
+
       ConsumeToken();
       break;
     }

Modified: cfe/trunk/lib/Sema/Sema.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/Sema.h?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/lib/Sema/Sema.h (original)
+++ cfe/trunk/lib/Sema/Sema.h Wed Aug  5 22:17:00 2009
@@ -833,9 +833,8 @@
                                SourceLocation *CommaLocs, 
                                SourceLocation RParenLoc);
 
-  ExprResult BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
-                                      SourceLocation MemberLoc,
-                                      IdentifierInfo &Member);
+  OwningExprResult BuildOverloadedArrowExpr(Scope *S, ExprArg Base,
+                                            SourceLocation OpLoc);
                
   /// Helpers for dealing with blocks and functions.
   void CheckFallThroughForFunctionDef(Decl *D, Stmt *Body);
@@ -1519,7 +1518,8 @@
                                                     tok::TokenKind OpKind,
                                                     SourceLocation MemberLoc,
                                                     IdentifierInfo &Member,
-                                                    DeclPtrTy ImplDecl);
+                                                    DeclPtrTy ImplDecl,
+                                                    const CXXScopeSpec *SS = 0);
   virtual void ActOnDefaultCtorInitializers(DeclPtrTy CDtorDecl);
   bool ConvertArgumentsForCall(CallExpr *Call, Expr *Fn,
                                FunctionDecl *FDecl,
@@ -1882,6 +1882,23 @@
                                                   SourceRange TypeRange,
                                                   SourceLocation CCLoc);
 
+  /// ActOnCXXEnterMemberScope - Called when a C++ class member accessor ('.'
+  /// or '->') is parsed. After this method is called, according to
+  /// [C++ 3.4.5p4], qualified-ids should be looked up in the contexts of both
+  /// the entire postfix-expression and the scope of the class of the object
+  /// expression.
+  /// 'SS' should be an empty CXXScopeSpec to be filled with the class's scope.
+  virtual OwningExprResult ActOnCXXEnterMemberScope(Scope *S, CXXScopeSpec &SS,
+                                                    ExprArg Base,
+                                                    tok::TokenKind OpKind);
+
+  /// ActOnCXXExitMemberScope - Called when a postfix-expression that previously
+  /// invoked ActOnCXXEnterMemberScope() is finished. 'SS' is the same
+  /// CXXScopeSpec that was passed to ActOnCXXEnterMemberScope. Used to
+  /// indicate that names should revert to being looked up in the defining
+  /// scope.
+  virtual void ActOnCXXExitMemberScope(Scope *S, const CXXScopeSpec &SS);
+
   /// ActOnCXXEnterDeclaratorScope - Called when a C++ scope specifier (global
   /// scope or nested-name-specifier) is parsed, part of a declarator-id.
   /// After this method is called, according to [C++ 3.4.3p3], names should be

Modified: cfe/trunk/lib/Sema/SemaCXXScopeSpec.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaCXXScopeSpec.cpp?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/lib/Sema/SemaCXXScopeSpec.cpp (original)
+++ cfe/trunk/lib/Sema/SemaCXXScopeSpec.cpp Wed Aug  5 22:17:00 2009
@@ -14,6 +14,7 @@
 #include "Sema.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/DeclTemplate.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/AST/NestedNameSpecifier.h"
 #include "clang/Parse/DeclSpec.h"
 #include "llvm/ADT/STLExtras.h"
@@ -337,6 +338,55 @@
                                      T.getTypePtr());
 }
 
+Action::OwningExprResult
+Sema::ActOnCXXEnterMemberScope(Scope *S, CXXScopeSpec &SS, ExprArg Base,
+                               tok::TokenKind OpKind) {
+  Expr *BaseExpr = (Expr*)Base.get();
+  assert(BaseExpr && "no record expansion");
+
+  QualType BaseType = BaseExpr->getType();
+  // FIXME: handle dependent types
+  if (BaseType->isDependentType())
+    return move(Base);
+
+  // C++ [over.match.oper]p8:
+  //   [...] When operator->returns, the operator-> is applied  to the value 
+  //   returned, with the original second operand.
+  if (OpKind == tok::arrow) {
+    while (BaseType->isRecordType()) {
+      Base = BuildOverloadedArrowExpr(S, move(Base), BaseExpr->getExprLoc());
+      BaseExpr = (Expr*)Base.get();
+      if (BaseExpr == NULL)
+          return ExprError();
+      BaseType = BaseExpr->getType();
+    }
+  }
+
+  if (BaseType->isPointerType())
+    BaseType = BaseType->getPointeeType();
+
+  // We could end up with various non-record types here, such as extended 
+  // vector types or Objective-C interfaces. Just return early and let
+  // ActOnMemberReferenceExpr do the work.
+  if (!BaseType->isRecordType())
+    return move(Base);
+
+  SS.setRange(BaseExpr->getSourceRange());
+  SS.setScopeRep(
+    NestedNameSpecifier::Create(Context, 0, false, BaseType.getTypePtr())
+    );
+
+  if (S)
+    ActOnCXXEnterDeclaratorScope(S,SS);
+  return move(Base);
+}
+
+void Sema::ActOnCXXExitMemberScope(Scope *S, const CXXScopeSpec &SS) {
+  if (S && SS.isSet())
+    ActOnCXXExitDeclaratorScope(S,SS);
+}
+
+
 /// ActOnCXXEnterDeclaratorScope - Called when a C++ scope specifier (global
 /// scope or nested-name-specifier) is parsed, part of a declarator-id.
 /// After this method is called, according to [C++ 3.4.3p3], names should be

Modified: cfe/trunk/lib/Sema/SemaExpr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaExpr.cpp?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/lib/Sema/SemaExpr.cpp (original)
+++ cfe/trunk/lib/Sema/SemaExpr.cpp Wed Aug  5 22:17:00 2009
@@ -2103,7 +2103,11 @@
 Sema::ActOnMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc,
                                tok::TokenKind OpKind, SourceLocation MemberLoc,
                                IdentifierInfo &Member,
-                               DeclPtrTy ObjCImpDecl) {
+                               DeclPtrTy ObjCImpDecl, const CXXScopeSpec *SS) {
+  // FIXME: handle the CXXScopeSpec for proper lookup of qualified-ids
+  if (SS && SS->isInvalid())
+    return ExprError();
+
   Expr *BaseExpr = Base.takeAs<Expr>();
   assert(BaseExpr && "no record expression");
 
@@ -2126,9 +2130,6 @@
       BaseType = PT->getPointeeType();
     else if (BaseType->isObjCObjectPointerType())
       ;
-    else if (getLangOptions().CPlusPlus && BaseType->isRecordType())
-      return Owned(BuildOverloadedArrowExpr(S, BaseExpr, OpLoc,
-                                            MemberLoc, Member));
     else
       return ExprError(Diag(MemberLoc,
                             diag::err_typecheck_member_reference_arrow)
@@ -2164,12 +2165,38 @@
                                BaseExpr->getSourceRange()))
       return ExprError();
 
+    DeclContext *DC = RDecl;
+    if (SS && SS->isSet()) {
+      // If the member name was a qualified-id, look into the
+      // nested-name-specifier.
+      DC = computeDeclContext(*SS, false);
+      
+      // FIXME: If DC is not computable, we should build a 
+      // CXXUnresolvedMemberExpr.
+      assert(DC && "Cannot handle non-computable dependent contexts in lookup");
+    }
+
     // The record definition is complete, now make sure the member is valid.
-    // FIXME: Qualified name lookup for C++ is a bit more complicated than this.
     LookupResult Result
-      = LookupQualifiedName(RDecl, DeclarationName(&Member),
+      = LookupQualifiedName(DC, DeclarationName(&Member),  
                             LookupMemberName, false);
 
+    if (SS && SS->isSet())
+    {
+      QualType BaseTypeCanon 
+        = Context.getCanonicalType(BaseType).getUnqualifiedType();
+      QualType MemberTypeCanon 
+        = Context.getCanonicalType(
+            Context.getTypeDeclType(
+                     dyn_cast<TypeDecl>(Result.getAsDecl()->getDeclContext())));
+
+      if (BaseTypeCanon != MemberTypeCanon &&
+          !IsDerivedFrom(BaseTypeCanon, MemberTypeCanon))
+        return ExprError(Diag(SS->getBeginLoc(),
+                              diag::err_not_direct_base_or_virtual)
+                         << MemberTypeCanon << BaseTypeCanon);
+    }
+
     if (!Result)
       return ExprError(Diag(MemberLoc, diag::err_typecheck_no_member)
                << &Member << BaseExpr->getSourceRange());

Modified: cfe/trunk/lib/Sema/SemaOverload.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaOverload.cpp?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/lib/Sema/SemaOverload.cpp (original)
+++ cfe/trunk/lib/Sema/SemaOverload.cpp Wed Aug  5 22:17:00 2009
@@ -4553,10 +4553,9 @@
 /// BuildOverloadedArrowExpr - Build a call to an overloaded @c operator->
 ///  (if one exists), where @c Base is an expression of class type and 
 /// @c Member is the name of the member we're trying to find.
-Action::ExprResult 
-Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
-                               SourceLocation MemberLoc,
-                               IdentifierInfo &Member) {
+Sema::OwningExprResult
+Sema::BuildOverloadedArrowExpr(Scope *S, ExprArg BaseIn, SourceLocation OpLoc) {
+  Expr *Base = static_cast<Expr *>(BaseIn.get());
   assert(Base->getType()->isRecordType() && "left-hand side must have class type");
   
   // C++ [over.ref]p1:
@@ -4569,15 +4568,13 @@
   DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(OO_Arrow);
   OverloadCandidateSet CandidateSet;
   const RecordType *BaseRecord = Base->getType()->getAs<RecordType>();
-  
+
   DeclContext::lookup_const_iterator Oper, OperEnd;
   for (llvm::tie(Oper, OperEnd) 
          = BaseRecord->getDecl()->lookup(OpName); Oper != OperEnd; ++Oper)
     AddMethodCandidate(cast<CXXMethodDecl>(*Oper), Base, 0, 0, CandidateSet,
                        /*SuppressUserConversions=*/false);
 
-  ExprOwningPtr<Expr> BasePtr(this, Base);
-
   // Perform overload resolution.
   OverloadCandidateSet::iterator Best;
   switch (BestViableFunction(CandidateSet, OpLoc, Best)) {
@@ -4588,34 +4585,34 @@
   case OR_No_Viable_Function:
     if (CandidateSet.empty())
       Diag(OpLoc, diag::err_typecheck_member_reference_arrow)
-        << BasePtr->getType() << BasePtr->getSourceRange();
+        << Base->getType() << Base->getSourceRange();
     else
       Diag(OpLoc, diag::err_ovl_no_viable_oper)
-        << "operator->" << BasePtr->getSourceRange();
+        << "operator->" << Base->getSourceRange();
     PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/false);
-    return true;
+    return ExprError();
 
   case OR_Ambiguous:
     Diag(OpLoc,  diag::err_ovl_ambiguous_oper)
-      << "operator->" << BasePtr->getSourceRange();
+      << "operator->" << Base->getSourceRange();
     PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true);
-    return true;
+    return ExprError();
 
   case OR_Deleted:
     Diag(OpLoc,  diag::err_ovl_deleted_oper)
       << Best->Function->isDeleted()
-      << "operator->" << BasePtr->getSourceRange();
+      << "operator->" << Base->getSourceRange();
     PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true);
-    return true;
+    return ExprError();
   }
 
   // Convert the object parameter.
   CXXMethodDecl *Method = cast<CXXMethodDecl>(Best->Function);
   if (PerformObjectArgumentInitialization(Base, Method))
-    return true;
+    return ExprError();
 
   // No concerns about early exits now.
-  BasePtr.take();
+  BaseIn.release();
 
   // Build the operator call.
   Expr *FnExpr = new (Context) DeclRefExpr(Method, Method->getType(),
@@ -4624,8 +4621,7 @@
   Base = new (Context) CXXOperatorCallExpr(Context, OO_Arrow, FnExpr, &Base, 1, 
                                  Method->getResultType().getNonReferenceType(),
                                  OpLoc);
-  return ActOnMemberReferenceExpr(S, ExprArg(*this, Base), OpLoc, tok::arrow,
-                                  MemberLoc, Member, DeclPtrTy()).release();
+  return Owned(Base);
 }
 
 /// FixOverloadedFunctionReference - E is an expression that refers to

Modified: cfe/trunk/lib/Sema/SemaTemplateInstantiateExpr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaTemplateInstantiateExpr.cpp?rev=78278&r1=78277&r2=78278&view=diff

==============================================================================
--- cfe/trunk/lib/Sema/SemaTemplateInstantiateExpr.cpp (original)
+++ cfe/trunk/lib/Sema/SemaTemplateInstantiateExpr.cpp Wed Aug  5 22:17:00 2009
@@ -1270,14 +1270,18 @@
   if (Base.isInvalid())
     return SemaRef.ExprError();
 
+  tok::TokenKind OpKind = E->isArrow() ? tok::arrow : tok::period;
+  CXXScopeSpec SS;
+  Base = SemaRef.ActOnCXXEnterMemberScope(0, SS, move(Base), OpKind);
   // FIXME: Instantiate the declaration name.
-  return SemaRef.ActOnMemberReferenceExpr(/*Scope=*/0,
+  Base = SemaRef.ActOnMemberReferenceExpr(/*Scope=*/0,
                                           move(Base), E->getOperatorLoc(),
-                                          E->isArrow()? tok::arrow 
-                                                      : tok::period,
+                                          OpKind,
                                           E->getMemberLoc(),
                               /*FIXME:*/*E->getMember().getAsIdentifierInfo(),
-                                   /*FIXME?*/Sema::DeclPtrTy::make((Decl*)0));
+                              /*FIXME?*/Sema::DeclPtrTy::make((Decl*)0));
+  SemaRef.ActOnCXXExitMemberScope(0, SS);
+  return move(Base);
 }
 
 //----------------------------------------------------------------------------

Added: cfe/trunk/test/SemaCXX/qual-id-test.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/qual-id-test.cpp?rev=78278&view=auto

==============================================================================
--- cfe/trunk/test/SemaCXX/qual-id-test.cpp (added)
+++ cfe/trunk/test/SemaCXX/qual-id-test.cpp Wed Aug  5 22:17:00 2009
@@ -0,0 +1,106 @@
+// RUN: clang-cc -fsyntax-only -verify %s 
+namespace A
+{
+    namespace B
+    {
+        struct base
+        {
+            void x() {}
+            void y() {}
+        };
+    }
+
+    struct member
+    {
+        void foo();
+    };
+
+    struct middleman
+    {
+        member * operator->() { return 0; }
+    };
+
+    struct sub : B::base
+    {
+        void x() {}
+        middleman operator->() { return middleman(); }
+    };
+}
+
+struct bad
+{
+  int x();
+};
+
+namespace C
+{
+    void fun()
+    {
+        A::sub a;
+
+        a.x();
+    
+        a.sub::x();
+        a.base::x();
+
+        a.B::base::x(); // expected-error{{use of undeclared identifier 'B'}}
+
+        a.A::sub::x();
+        a.A::B::base::x();
+
+        a.bad::x(); // expected-error{{type 'struct bad' is not a direct or virtual base of ''struct A::sub''}}
+
+        a->foo();
+        a->member::foo();
+        a->A::member::foo();
+    }
+
+    void fun2()
+    {
+        A::sub *a;
+
+        a->x();
+
+        a->sub::x();
+        a->base::x();
+
+        a->B::base::x(); // expected-error{{use of undeclared identifier 'B'}}
+
+        a->A::sub::x();
+        a->A::B::base::x();
+
+        a->bad::x(); // expected-error{{type 'struct bad' is not a direct or virtual base of ''struct A::sub''}}
+
+        (*a)->foo();
+        (*a)->member::foo();
+        (*a)->A::member::foo();
+    }
+
+    void fun3()
+    {
+        int i;
+        i.foo(); // expected-error{{member reference base type 'int' is not a structure or union}}
+    }
+
+    template<typename T>
+    void fun4()
+    {
+        T a;
+        a.x();
+        a->foo();
+
+        // Things that work for the wrong reason
+        a.A::sub::x();
+        a.A::B::base::x();
+        a->A::member::foo();
+
+        // Things that work, but shouldn't
+        a.bad::x();
+
+        // Things that fail, but shouldn't
+        a.sub::x(); // expected-error{{use of undeclared identifier 'sub'}}
+        a.base::x(); // expected-error{{use of undeclared identifier 'base'}}
+        a.B::base::x(); // expected-error{{use of undeclared identifier 'B'}}
+        a->member::foo(); // expected-error{{use of undeclared identifier 'member'}}
+    }
+}

Propchange: cfe/trunk/test/SemaCXX/qual-id-test.cpp

------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cfe/trunk/test/SemaCXX/qual-id-test.cpp

------------------------------------------------------------------------------
    svn:keywords = Id

Propchange: cfe/trunk/test/SemaCXX/qual-id-test.cpp

------------------------------------------------------------------------------
    svn:mime-type = text/plain





More information about the cfe-commits mailing list