[cfe-commits] r80167 - in /cfe/trunk: include/clang/AST/Expr.h include/clang/AST/ExprCXX.h include/clang/AST/StmtNodes.def lib/AST/Expr.cpp lib/AST/StmtPrinter.cpp lib/AST/StmtProfile.cpp lib/Analysis/GRExprEngine.cpp lib/CodeGen/CGCXX.cpp lib/CodeGen/CGExpr.cpp lib/Sema/SemaChecking.cpp lib/Sema/SemaExpr.cpp lib/Sema/TreeTransform.h test/CXX/class.derived/class.virtual/p12.cpp
Douglas Gregor
dgregor at apple.com
Wed Aug 26 15:36:53 PDT 2009
Author: dgregor
Date: Wed Aug 26 17:36:53 2009
New Revision: 80167
URL: http://llvm.org/viewvc/llvm-project?rev=80167&view=rev
Log:
When a member reference expression includes a qualifier on the member
name, e.g.,
x->Base::f()
retain the qualifier (and its source range information) in a new
subclass of MemberExpr called CXXQualifiedMemberExpr. Provide
construction, transformation, profiling, printing, etc., for this new
expression type.
When a virtual function is called via a qualified name, don't emit a
virtual call. Instead, call that function directly. Mike, could you
add a CodeGen test for this, too?
Added:
cfe/trunk/test/CXX/class.derived/class.virtual/p12.cpp
Modified:
cfe/trunk/include/clang/AST/Expr.h
cfe/trunk/include/clang/AST/ExprCXX.h
cfe/trunk/include/clang/AST/StmtNodes.def
cfe/trunk/lib/AST/Expr.cpp
cfe/trunk/lib/AST/StmtPrinter.cpp
cfe/trunk/lib/AST/StmtProfile.cpp
cfe/trunk/lib/Analysis/GRExprEngine.cpp
cfe/trunk/lib/CodeGen/CGCXX.cpp
cfe/trunk/lib/CodeGen/CGExpr.cpp
cfe/trunk/lib/Sema/SemaChecking.cpp
cfe/trunk/lib/Sema/SemaExpr.cpp
cfe/trunk/lib/Sema/TreeTransform.h
Modified: cfe/trunk/include/clang/AST/Expr.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/Expr.h?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/include/clang/AST/Expr.h (original)
+++ cfe/trunk/include/clang/AST/Expr.h Wed Aug 26 17:36:53 2009
@@ -33,6 +33,7 @@
class BlockDecl;
class CXXOperatorCallExpr;
class CXXMemberCallExpr;
+ class CXXQualifiedMemberExpr;
/// Expr - This represents one expression. Note that Expr's are subclasses of
/// Stmt. This allows an expression to be transparently used any place a Stmt
@@ -1054,6 +1055,14 @@
/// IsArrow - True if this is "X->F", false if this is "X.F".
bool IsArrow;
+
+protected:
+ MemberExpr(StmtClass SC, Expr *base, bool isarrow, NamedDecl *memberdecl,
+ SourceLocation l, QualType ty)
+ : Expr(SC, ty,
+ base->isTypeDependent(), base->isValueDependent()),
+ Base(base), MemberDecl(memberdecl), MemberLoc(l), IsArrow(isarrow) {}
+
public:
MemberExpr(Expr *base, bool isarrow, NamedDecl *memberdecl, SourceLocation l,
QualType ty)
@@ -1094,9 +1103,11 @@
virtual SourceLocation getExprLoc() const { return MemberLoc; }
static bool classof(const Stmt *T) {
- return T->getStmtClass() == MemberExprClass;
+ return T->getStmtClass() == MemberExprClass ||
+ T->getStmtClass() == CXXQualifiedMemberExprClass;
}
static bool classof(const MemberExpr *) { return true; }
+ static bool classof(const CXXQualifiedMemberExpr *) { return true; }
// Iterators
virtual child_iterator child_begin();
Modified: cfe/trunk/include/clang/AST/ExprCXX.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/ExprCXX.h?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/include/clang/AST/ExprCXX.h (original)
+++ cfe/trunk/include/clang/AST/ExprCXX.h Wed Aug 26 17:36:53 2009
@@ -1285,7 +1285,41 @@
virtual child_iterator child_end();
};
-/// \brief
+/// \brief Represents a C++ member access expression that was written using
+/// a qualified name, e.g., "x->Base::f()".
+class CXXQualifiedMemberExpr : public MemberExpr {
+ /// QualifierRange - The source range that covers the
+ /// nested-name-specifier.
+ SourceRange QualifierRange;
+
+ /// \brief The nested-name-specifier that qualifies this declaration
+ /// name.
+ NestedNameSpecifier *Qualifier;
+
+public:
+ CXXQualifiedMemberExpr(Expr *base, bool isarrow, NestedNameSpecifier *Qual,
+ SourceRange QualRange, NamedDecl *memberdecl,
+ SourceLocation l, QualType ty)
+ : MemberExpr(CXXQualifiedMemberExprClass, base, isarrow, memberdecl, l, ty),
+ QualifierRange(QualRange), Qualifier(Qual) { }
+
+ /// \brief Retrieve the source range of the nested-name-specifier that
+ /// qualifies the member name.
+ SourceRange getQualifierRange() const { return QualifierRange; }
+
+ /// \brief Retrieve the nested-name-specifier that qualifies the
+ /// member reference expression.
+ NestedNameSpecifier *getQualifier() const { return Qualifier; }
+
+ static bool classof(const Stmt *T) {
+ return T->getStmtClass() == CXXQualifiedMemberExprClass;
+ }
+ static bool classof(const CXXQualifiedMemberExpr *) { return true; }
+};
+
+/// \brief Represents a C++ member access expression where the actual member
+/// referenced could not be resolved, e.g., because the base expression or the
+/// member name was dependent.
class CXXUnresolvedMemberExpr : public Expr {
/// \brief The expression for the base pointer or class reference,
/// e.g., the \c x in x.f.
Modified: cfe/trunk/include/clang/AST/StmtNodes.def
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/StmtNodes.def?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/include/clang/AST/StmtNodes.def (original)
+++ cfe/trunk/include/clang/AST/StmtNodes.def Wed Aug 26 17:36:53 2009
@@ -134,6 +134,7 @@
EXPR(CXXExprWithTemporaries , Expr)
EXPR(CXXTemporaryObjectExpr , CXXConstructExpr)
EXPR(CXXUnresolvedConstructExpr, Expr)
+EXPR(CXXQualifiedMemberExpr, MemberExpr)
EXPR(CXXUnresolvedMemberExpr, Expr)
// Obj-C Expressions.
Modified: cfe/trunk/lib/AST/Expr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/Expr.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/AST/Expr.cpp (original)
+++ cfe/trunk/lib/AST/Expr.cpp Wed Aug 26 17:36:53 2009
@@ -497,6 +497,7 @@
}
case MemberExprClass:
+ case CXXQualifiedMemberExprClass:
// If the base pointer or element is to a volatile pointer/field, accessing
// it is a side effect.
if (getType().isVolatileQualified())
@@ -684,7 +685,8 @@
return LV_Valid;
break;
}
- case MemberExprClass: {
+ case MemberExprClass:
+ case CXXQualifiedMemberExprClass: {
const MemberExpr *m = cast<MemberExpr>(this);
if (Ctx.getLangOptions().CPlusPlus) { // C++ [expr.ref]p4:
NamedDecl *Member = m->getMemberDecl();
@@ -948,7 +950,8 @@
return true;
return false;
}
- case MemberExprClass: {
+ case MemberExprClass:
+ case CXXQualifiedMemberExprClass: {
const MemberExpr *M = cast<MemberExpr>(this);
return !M->isArrow() && M->getBase()->hasGlobalStorage();
}
@@ -992,7 +995,8 @@
}
return false;
}
- case MemberExprClass: {
+ case MemberExprClass:
+ case CXXQualifiedMemberExprClass: {
const MemberExpr *M = cast<MemberExpr>(this);
return M->getBase()->isOBJCGCCandidate(Ctx);
}
Modified: cfe/trunk/lib/AST/StmtPrinter.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/StmtPrinter.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/AST/StmtPrinter.cpp (original)
+++ cfe/trunk/lib/AST/StmtPrinter.cpp Wed Aug 26 17:36:53 2009
@@ -1126,6 +1126,16 @@
OS << ")";
}
+void StmtPrinter::VisitCXXQualifiedMemberExpr(CXXQualifiedMemberExpr *Node) {
+ // FIXME: Suppress printing implicit bases (like "this")
+ PrintExpr(Node->getBase());
+ OS << (Node->isArrow() ? "->" : ".");
+ // FIXME: Suppress printing references to unnamed objects
+ // representing anonymous unions/structs
+ Node->getQualifier()->print(OS, Policy);
+ OS << Node->getMemberDecl()->getNameAsString();
+}
+
void StmtPrinter::VisitCXXUnresolvedMemberExpr(CXXUnresolvedMemberExpr *Node) {
PrintExpr(Node->getBase());
OS << (Node->isArrow() ? "->" : ".");
Modified: cfe/trunk/lib/AST/StmtProfile.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/StmtProfile.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/AST/StmtProfile.cpp (original)
+++ cfe/trunk/lib/AST/StmtProfile.cpp Wed Aug 26 17:36:53 2009
@@ -546,6 +546,11 @@
VisitType(S->getTypeAsWritten());
}
+void StmtProfiler::VisitCXXQualifiedMemberExpr(CXXQualifiedMemberExpr *S) {
+ VisitMemberExpr(S);
+ VisitNestedNameSpecifier(S->getQualifier());
+}
+
void StmtProfiler::VisitCXXUnresolvedMemberExpr(CXXUnresolvedMemberExpr *S) {
VisitExpr(S);
ID.AddBoolean(S->isArrow());
Modified: cfe/trunk/lib/Analysis/GRExprEngine.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Analysis/GRExprEngine.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/Analysis/GRExprEngine.cpp (original)
+++ cfe/trunk/lib/Analysis/GRExprEngine.cpp Wed Aug 26 17:36:53 2009
@@ -408,6 +408,7 @@
break;
case Stmt::MemberExprClass:
+ case Stmt::CXXQualifiedMemberExprClass:
VisitMemberExpr(cast<MemberExpr>(S), Pred, Dst, false);
break;
@@ -515,6 +516,7 @@
return;
case Stmt::MemberExprClass:
+ case Stmt::CXXQualifiedMemberExprClass:
VisitMemberExpr(cast<MemberExpr>(Ex), Pred, Dst, true);
return;
Modified: cfe/trunk/lib/CodeGen/CGCXX.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/CodeGen/CGCXX.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/CodeGen/CGCXX.cpp (original)
+++ cfe/trunk/lib/CodeGen/CGCXX.cpp Wed Aug 26 17:36:53 2009
@@ -212,11 +212,13 @@
This = BaseLV.getAddress();
}
+ // C++ [class.virtual]p12:
+ // Explicit qualification with the scope operator (5.1) suppresses the
+ // virtual call mechanism.
llvm::Value *Callee;
- // FIXME: Someone needs to keep track of the qualifications.
- if (MD->isVirtual() /* && !ME->NotQualified() */)
+ if (MD->isVirtual() && !isa<CXXQualifiedMemberExpr>(CE)) {
Callee = BuildVirtualCall(MD, This, Ty);
- else
+ } else
Callee = CGM.GetAddrOfFunction(GlobalDecl(MD), Ty);
return EmitCXXMemberCall(MD, Callee, This,
Modified: cfe/trunk/lib/CodeGen/CGExpr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/CodeGen/CGExpr.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/CodeGen/CGExpr.cpp (original)
+++ cfe/trunk/lib/CodeGen/CGExpr.cpp Wed Aug 26 17:36:53 2009
@@ -240,7 +240,9 @@
return EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E));
case Expr::ExtVectorElementExprClass:
return EmitExtVectorElementExpr(cast<ExtVectorElementExpr>(E));
- case Expr::MemberExprClass: return EmitMemberExpr(cast<MemberExpr>(E));
+ case Expr::MemberExprClass:
+ case Stmt::CXXQualifiedMemberExprClass:
+ return EmitMemberExpr(cast<MemberExpr>(E));
case Expr::CompoundLiteralExprClass:
return EmitCompoundLiteralLValue(cast<CompoundLiteralExpr>(E));
case Expr::ConditionalOperatorClass:
Modified: cfe/trunk/lib/Sema/SemaChecking.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaChecking.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaChecking.cpp (original)
+++ cfe/trunk/lib/Sema/SemaChecking.cpp Wed Aug 26 17:36:53 2009
@@ -1421,7 +1421,8 @@
}
// Accesses to members are potential references to data on the stack.
- case Stmt::MemberExprClass: {
+ case Stmt::MemberExprClass:
+ case Stmt::CXXQualifiedMemberExprClass: {
MemberExpr *M = cast<MemberExpr>(E);
// Check for indirect access. We only want direct field accesses.
Modified: cfe/trunk/lib/Sema/SemaExpr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaExpr.cpp?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaExpr.cpp (original)
+++ cfe/trunk/lib/Sema/SemaExpr.cpp Wed Aug 26 17:36:53 2009
@@ -631,6 +631,7 @@
if (BaseAddrSpace != MemberType.getAddressSpace())
MemberType = Context.getAddrSpaceQualType(MemberType, BaseAddrSpace);
MarkDeclarationReferenced(Loc, *FI);
+ // FIXME: Might this end up being a qualified name?
Result = new (Context) MemberExpr(Result, BaseObjectIsPointer, *FI,
OpLoc, MemberType);
BaseObjectIsPointer = false;
@@ -874,6 +875,19 @@
return false;
}
+/// \brief Build a MemberExpr or CXXQualifiedMemberExpr, as appropriate.
+static MemberExpr *BuildMemberExpr(ASTContext &C, Expr *Base, bool isArrow,
+ const CXXScopeSpec *SS, NamedDecl *Member,
+ SourceLocation Loc, QualType Ty) {
+ if (SS && SS->isSet())
+ return new (C) CXXQualifiedMemberExpr(Base, isArrow,
+ (NestedNameSpecifier *)SS->getScopeRep(),
+ SS->getRange(),
+ Member, Loc, Ty);
+
+ return new (C) MemberExpr(Base, isArrow, Member, Loc, Ty);
+}
+
/// \brief Complete semantic analysis for a reference to the given declaration.
Sema::OwningExprResult
Sema::BuildDeclarationNameExpr(SourceLocation Loc, NamedDecl *D,
@@ -985,8 +999,8 @@
return ExprError();
if (DiagnoseUseOfDecl(D, Loc))
return ExprError();
- return Owned(new (Context) MemberExpr(This, true, D,
- Loc, MemberType));
+ return Owned(BuildMemberExpr(Context, This, true, SS, D,
+ Loc, MemberType));
}
}
}
@@ -1953,7 +1967,7 @@
return FindMethodInNestedImplementations(IFace->getSuperClass(), Sel);
return Method;
}
-
+
Action::OwningExprResult
Sema::BuildMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc,
tok::TokenKind OpKind, SourceLocation MemberLoc,
@@ -2112,37 +2126,37 @@
MarkDeclarationReferenced(MemberLoc, FD);
if (PerformObjectMemberConversion(BaseExpr, FD))
return ExprError();
- return Owned(new (Context) MemberExpr(BaseExpr, OpKind == tok::arrow, FD,
- MemberLoc, MemberType));
+ return Owned(BuildMemberExpr(Context, BaseExpr, OpKind == tok::arrow, SS,
+ FD, MemberLoc, MemberType));
}
if (VarDecl *Var = dyn_cast<VarDecl>(MemberDecl)) {
MarkDeclarationReferenced(MemberLoc, MemberDecl);
- return Owned(new (Context) MemberExpr(BaseExpr, OpKind == tok::arrow,
- Var, MemberLoc,
- Var->getType().getNonReferenceType()));
+ return Owned(BuildMemberExpr(Context, BaseExpr, OpKind == tok::arrow, SS,
+ Var, MemberLoc,
+ Var->getType().getNonReferenceType()));
}
if (FunctionDecl *MemberFn = dyn_cast<FunctionDecl>(MemberDecl)) {
MarkDeclarationReferenced(MemberLoc, MemberDecl);
- return Owned(new (Context) MemberExpr(BaseExpr, OpKind == tok::arrow,
- MemberFn, MemberLoc,
- MemberFn->getType()));
+ return Owned(BuildMemberExpr(Context, BaseExpr, OpKind == tok::arrow, SS,
+ MemberFn, MemberLoc,
+ MemberFn->getType()));
}
if (FunctionTemplateDecl *FunTmpl
= dyn_cast<FunctionTemplateDecl>(MemberDecl)) {
MarkDeclarationReferenced(MemberLoc, MemberDecl);
- return Owned(new (Context) MemberExpr(BaseExpr, OpKind == tok::arrow,
- FunTmpl, MemberLoc,
- Context.OverloadTy));
+ return Owned(BuildMemberExpr(Context, BaseExpr, OpKind == tok::arrow, SS,
+ FunTmpl, MemberLoc,
+ Context.OverloadTy));
}
if (OverloadedFunctionDecl *Ovl
= dyn_cast<OverloadedFunctionDecl>(MemberDecl))
- return Owned(new (Context) MemberExpr(BaseExpr, OpKind == tok::arrow, Ovl,
- MemberLoc, Context.OverloadTy));
+ return Owned(BuildMemberExpr(Context, BaseExpr, OpKind == tok::arrow, SS,
+ Ovl, MemberLoc, Context.OverloadTy));
if (EnumConstantDecl *Enum = dyn_cast<EnumConstantDecl>(MemberDecl)) {
MarkDeclarationReferenced(MemberLoc, MemberDecl);
- return Owned(new (Context) MemberExpr(BaseExpr, OpKind == tok::arrow,
- Enum, MemberLoc, Enum->getType()));
+ return Owned(BuildMemberExpr(Context, BaseExpr, OpKind == tok::arrow, SS,
+ Enum, MemberLoc, Enum->getType()));
}
if (isa<TypeDecl>(MemberDecl))
return ExprError(Diag(MemberLoc,diag::err_typecheck_member_reference_type)
@@ -4802,6 +4816,7 @@
case Stmt::QualifiedDeclRefExprClass:
return cast<DeclRefExpr>(E)->getDecl();
case Stmt::MemberExprClass:
+ case Stmt::CXXQualifiedMemberExprClass:
// If this is an arrow operator, the address is an offset from
// the base's value, so the object the base refers to is
// irrelevant.
Modified: cfe/trunk/lib/Sema/TreeTransform.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/TreeTransform.h?rev=80167&r1=80166&r2=80167&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/TreeTransform.h (original)
+++ cfe/trunk/lib/Sema/TreeTransform.h Wed Aug 26 17:36:53 2009
@@ -1421,6 +1421,28 @@
RParenLoc);
}
+ /// \brief Build a new qualified member access expression.
+ ///
+ /// By default, performs semantic analysis to build the new expression.
+ /// Subclasses may override this routine to provide different behavior.
+ OwningExprResult RebuildCXXQualifiedMemberExpr(ExprArg Base,
+ SourceLocation OpLoc,
+ bool isArrow,
+ NestedNameSpecifier *Qualifier,
+ SourceRange QualifierRange,
+ SourceLocation MemberLoc,
+ NamedDecl *Member) {
+ CXXScopeSpec SS;
+ SS.setRange(QualifierRange);
+ SS.setScopeRep(Qualifier);
+ return getSema().ActOnMemberReferenceExpr(/*Scope=*/0, move(Base), OpLoc,
+ isArrow? tok::arrow : tok::period,
+ MemberLoc,
+ /*FIXME*/*Member->getIdentifier(),
+ /*FIXME?*/Sema::DeclPtrTy::make((Decl*)0),
+ &SS);
+ }
+
/// \brief Build a new member reference expression.
///
/// By default, performs semantic analysis to build the new expression.
@@ -3992,6 +4014,44 @@
E->getRParenLoc());
}
+template<typename Derived>
+Sema::OwningExprResult
+TreeTransform<Derived>::TransformCXXQualifiedMemberExpr(
+ CXXQualifiedMemberExpr *E) {
+ OwningExprResult Base = getDerived().TransformExpr(E->getBase());
+ if (Base.isInvalid())
+ return SemaRef.ExprError();
+
+ NamedDecl *Member
+ = cast_or_null<NamedDecl>(getDerived().TransformDecl(E->getMemberDecl()));
+ if (!Member)
+ return SemaRef.ExprError();
+
+ NestedNameSpecifier *Qualifier
+ = getDerived().TransformNestedNameSpecifier(E->getQualifier(),
+ E->getQualifierRange());
+ if (Qualifier == 0)
+ return SemaRef.ExprError();
+
+ if (!getDerived().AlwaysRebuild() &&
+ Base.get() == E->getBase() &&
+ Member == E->getMemberDecl() &&
+ Qualifier == E->getQualifier())
+ return SemaRef.Owned(E->Retain());
+
+ // FIXME: Bogus source location for the operator
+ SourceLocation FakeOperatorLoc
+ = SemaRef.PP.getLocForEndOfToken(E->getBase()->getSourceRange().getEnd());
+
+ return getDerived().RebuildCXXQualifiedMemberExpr(move(Base),
+ FakeOperatorLoc,
+ E->isArrow(),
+ Qualifier,
+ E->getQualifierRange(),
+ E->getMemberLoc(),
+ Member);
+}
+
template<typename Derived>
Sema::OwningExprResult
TreeTransform<Derived>::TransformCXXUnresolvedMemberExpr(
Added: cfe/trunk/test/CXX/class.derived/class.virtual/p12.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CXX/class.derived/class.virtual/p12.cpp?rev=80167&view=auto
==============================================================================
--- cfe/trunk/test/CXX/class.derived/class.virtual/p12.cpp (added)
+++ cfe/trunk/test/CXX/class.derived/class.virtual/p12.cpp Wed Aug 26 17:36:53 2009
@@ -0,0 +1,19 @@
+// RUN: clang-cc -ast-print %s | FileCheck %s
+
+// CHECK: test12_A::foo()
+struct test12_A {
+ virtual void foo();
+
+ void bar() {
+ test12_A::foo();
+ }
+};
+
+// CHECK: xp->test24_B::wibble()
+struct test24_B {
+ virtual void wibble();
+};
+
+void foo(test24_B *xp) {
+ xp->test24_B::wibble();
+}
More information about the cfe-commits
mailing list