//===--- ArgumentList.h - Function and subscript argument lists -*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file defines the Argument and ArgumentList classes and support logic. // //===----------------------------------------------------------------------===// #ifndef SWIFT_AST_ARGUMENTLIST_H #define SWIFT_AST_ARGUMENTLIST_H #include "swift/AST/ASTAllocated.h" #include "swift/AST/Types.h" #include "swift/Basic/Debug.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/TrailingObjects.h" #include namespace swift { /// Forward declared trampoline for Expr::getType. Type __Expr_getType(Expr *E); /// Represents a single argument in an argument list, including its label /// information and inout-ness. /// /// \code /// foo.bar(arg, label: arg2, other: &arg3) /// ^^^ ^^^^^^^^^^^^ /// an unlabeled 'other' is the label, 'arg3' is the expr, /// argument and isInout() is true. /// \endcode class Argument final { SourceLoc LabelLoc; Identifier Label; Expr *ArgExpr; // TODO: Store inout bit here. public: Argument(SourceLoc labelLoc, Identifier label, Expr *expr) : LabelLoc(labelLoc), Label(label), ArgExpr(expr) {} /// Make an unlabeled argument. static Argument unlabeled(Expr *expr) { return Argument(SourceLoc(), Identifier(), expr); } /// Make an implicit unlabeled 'inout' argument. static Argument implicitInOut(ASTContext &ctx, Expr *expr); SourceLoc getStartLoc() const { return getSourceRange().Start; } SourceLoc getEndLoc() const { return getSourceRange().End; } SourceRange getSourceRange() const; /// If the argument has a label with location information, returns the /// location. /// /// Note this may be present even if the label is empty, e.g for multiple /// trailing closures where labels are required such as '_: {}'. SourceLoc getLabelLoc() const { return LabelLoc; } /// The argument label written in the call. Identifier getLabel() const { return Label; } /// Whether the argument has a non-empty label. Note that this returns `false` /// for an explicitly specified empty label e.g `_: {}` for a trailing /// closure. bool hasLabel() const { return !Label.empty(); } /// Set a new argument label. /// /// Note this is marked \c & to prevent its use on an rvalue Argument vended /// from an ArgumentList. void setLabel(Identifier newLabel) & { Label = newLabel; } /// The argument expression. Expr *getExpr() const { return ArgExpr; } /// Set a new argument expression. /// /// Note this is marked \c & to prevent its use on an rvalue Argument vended /// from an ArgumentList. void setExpr(Expr *newExpr) & { ArgExpr = newExpr; } /// Whether the argument is \c inout, denoted with the '&' prefix. bool isInOut() const; /// Whether the argument is a compile-time constant value. bool isConst() const; bool operator==(const Argument &other) const { return LabelLoc == other.LabelLoc && Label == other.Label && ArgExpr == other.ArgExpr; } bool operator!=(const Argument &other) const { return !(*this == other); } }; /// Represents the argument list of a function call or subscript access. /// \code /// foo.bar(arg, label: arg2, other: &arg3) /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ /// \endcode /// /// Note that arguments are split up and stored in separate buffers to optimize /// ArgumentList's memory footprint for the unlabeled cases. class alignas(Argument) ArgumentList final : public ASTAllocated, private llvm::TrailingObjects { friend TrailingObjects; SourceLoc LParenLoc; SourceLoc RParenLoc; /// The number of arguments in the list. unsigned NumArgs : 16; /// The index of the first trailing closure, or \c NumArgs if there are /// either no trailing closures, or the trailing closure info is present in /// the original args. unsigned RawFirstTrailingClosureIndex : 16; /// Whether an original set of arguments are available. bool HasOriginalArgs : 1; /// Whether the argument list was implicitly generated. bool IsImplicit : 1; /// Whether any of the arguments are labeled. bool HasLabels : 1; /// Whether any of the arguments have labels with location info. bool HasLabelLocs : 1; ArgumentList(SourceLoc lParenLoc, SourceLoc rParenLoc, unsigned numArgs, std::optional firstTrailingClosureIndex, ArgumentList *originalArgs, bool isImplicit, bool hasLabels, bool hasLabelLocs) : LParenLoc(lParenLoc), RParenLoc(rParenLoc), NumArgs(numArgs), HasOriginalArgs(originalArgs), IsImplicit(isImplicit), HasLabels(hasLabels), HasLabelLocs(hasLabelLocs) { assert(LParenLoc.isValid() == RParenLoc.isValid()); assert(!(firstTrailingClosureIndex && originalArgs) && "Cannot have trailing closure info if original args present"); assert(!(originalArgs && originalArgs->getStoredOriginalArgs()) && "Cannot chain original argument lists"); assert(NumArgs == numArgs && "Num args truncated"); assert(!firstTrailingClosureIndex || *firstTrailingClosureIndex < numArgs && "Invalid trailing closure index"); RawFirstTrailingClosureIndex = firstTrailingClosureIndex.value_or(numArgs); } ArgumentList(const ArgumentList &) = delete; ArgumentList &operator=(const ArgumentList &) = delete; size_t numTrailingObjects(OverloadToken) const { return NumArgs; } size_t numTrailingObjects(OverloadToken) const { return HasLabels ? NumArgs : 0; } size_t numTrailingObjects(OverloadToken) const { return HasLabelLocs ? NumArgs : 0; } size_t numTrailingObjects(OverloadToken) const { return HasOriginalArgs ? 1 : 0; } ArrayRef getExprsBuffer() const { return {getTrailingObjects(), NumArgs}; } MutableArrayRef getExprsBuffer() { return {getTrailingObjects(), NumArgs}; } ArrayRef getLabelsBuffer() const { assert(HasLabels); return {getTrailingObjects(), NumArgs}; } MutableArrayRef getLabelsBuffer() { assert(HasLabels); return {getTrailingObjects(), NumArgs}; } ArrayRef getLabelLocsBuffer() const { assert(HasLabelLocs); return {getTrailingObjects(), NumArgs}; } MutableArrayRef getLabelLocsBuffer() { assert(HasLabelLocs); return {getTrailingObjects(), NumArgs}; } ArgumentList *getStoredOriginalArgs() const { if (!HasOriginalArgs) return nullptr; return *getTrailingObjects(); } public: /// Create a new ArgumentList. /// /// \param lParenLoc The location of the opening '('. Note that for a /// subscript argument list, this will be for the opening '['. /// \param args The list of arguments in the call. /// \param rParenLoc The location of the closing ')'. Note that for a /// subscript argument list, this will be for the closing ']'. /// \param firstTrailingClosureIndex The index of the first trailing closure. /// \param isImplicit Whether this is an implicitly generated argument list. /// \param originalArgs An original argument list prior to being rewritten by /// the expression type-checker. Note \p firstTrailingClosureIndex must be /// \c None if an original argument list is available. /// \param arena The arena to allocate the ArgumentList in. static ArgumentList * create(ASTContext &ctx, SourceLoc lParenLoc, ArrayRef args, SourceLoc rParenLoc, std::optional firstTrailingClosureIndex, bool isImplicit, ArgumentList *originalArgs = nullptr, AllocationArena arena = AllocationArena::Permanent); /// Create a new explicit parsed ArgumentList. /// /// \param lParenLoc The location of the opening '('. Note that for a /// subscript argument list, this will be for the opening '['. /// \param args The list of arguments in the call. /// \param rParenLoc The location of the closing ')'. Note that for a /// subscript argument list, this will be for the closing ']'. /// \param firstTrailingClosureIndex The index of the first trailing closure. static ArgumentList * createParsed(ASTContext &ctx, SourceLoc lParenLoc, ArrayRef args, SourceLoc rParenLoc, std::optional firstTrailingClosureIndex); /// Create a new type-checked ArgumentList from an original set of arguments. /// /// \param originalArgs An original argument list prior to being rewritten by /// the type-checker. /// \param newArgs The new set of arguments for the call. static ArgumentList *createTypeChecked(ASTContext &ctx, ArgumentList *originalArgs, ArrayRef newArgs); /// Create a new implicit ArgumentList. /// /// \param lParenLoc The location of the opening '('. Note that for a /// subscript argument list, this will be for the opening '['. /// \param args The list of arguments in the call. /// \param rParenLoc The location of the closing ')'. Note that for a /// subscript argument list, this will be for the closing ']'. /// \param arena The arena to allocate the ArgumentList in. static ArgumentList *createImplicit( ASTContext &ctx, SourceLoc lParenLoc, ArrayRef args, SourceLoc rParenLoc, std::optional firstTrailingClosureIndex = std::nullopt, AllocationArena arena = AllocationArena::Permanent); /// Create a new implicit ArgumentList with a set of \p args. static ArgumentList *createImplicit( ASTContext &ctx, ArrayRef args, std::optional firstTrailingClosureIndex = std::nullopt, AllocationArena arena = AllocationArena::Permanent); /// Create a new implicit ArgumentList with a single labeled argument /// expression. static ArgumentList *forImplicitSingle(ASTContext &ctx, Identifier label, Expr *arg); /// Create a new implicit ArgumentList with a set of unlabeled arguments. static ArgumentList *forImplicitUnlabeled(ASTContext &ctx, ArrayRef argExprs); /// Create a new implicit ArgumentList with a set of argument expressions, /// and a DeclNameRef from which to infer the argument labels. static ArgumentList *forImplicitCallTo(DeclNameRef fnNameRef, ArrayRef argExprs, ASTContext &ctx); /// Create a new implicit ArgumentList with a set of argument expressions, /// and a ParameterList from which to infer the argument labels. static ArgumentList *forImplicitCallTo(ParameterList *params, ArrayRef argExprs, ASTContext &ctx); /// The location of the opening '('. Note that for a subscript argument list, /// this will be for the opening '['. SourceLoc getLParenLoc() const { return LParenLoc; } /// The location of the closing ')'. Note that for a subscript argument list, /// this will be for the closing ']'. SourceLoc getRParenLoc() const { return RParenLoc; } /// Whether this is an implicitly generated argument list, not written in the /// source. bool isImplicit() const { return IsImplicit; } /// Whether the argument list has any non-empty argument labels. bool hasAnyArgumentLabels() const { return HasLabels; } SourceLoc getLoc() const; SourceLoc getStartLoc() const { return getSourceRange().Start; } SourceLoc getEndLoc() const { return getSourceRange().End; } SourceRange getSourceRange() const; /// Retrieve the argument expression at a given index. Expr *getExpr(unsigned idx) const { return getExprsBuffer()[idx]; } void setExpr(unsigned idx, Expr *newExpr) { getExprsBuffer()[idx] = newExpr; } /// Retrieve the argument label at a given index. Identifier getLabel(unsigned idx) const { if (!HasLabels) return Identifier(); return getLabelsBuffer()[idx]; } /// Retrieve the argument label location at a given index. SourceLoc getLabelLoc(unsigned idx) const { if (!HasLabelLocs) return SourceLoc(); return getLabelLocsBuffer()[idx]; } /// Retrieve the argument at a given index. Argument get(unsigned idx) const { return Argument(getLabelLoc(idx), getLabel(idx), getExpr(idx)); } // MARK: Iterator Logic unsigned size() const { return NumArgs; } bool empty() const { return size() == 0; } class iterator final : public llvm::indexed_accessor_iterator { public: iterator(const ArgumentList *argList, unsigned idx) : indexed_accessor_iterator(argList, idx) {} Argument operator*() const { assert(getBase()); return getBase()->get(getIndex()); } Argument operator[](unsigned i) const { assert(getBase()); return getBase()->get(getIndex() + i); } }; iterator begin() const { return {this, 0}; } iterator end() const { return {this, size()}; } Argument front() const { return get(0); } Argument back() const { return get(size() - 1); } Argument operator[](unsigned idx) const { return get(idx); } // MARK: Trailing Closure Queries // // Trailing closure queries cannot be done on a type-checked argument list, // instead they must be done on the original argument list prior to rewriting // in CSApply. This can be obtained by querying the \c getOriginalArgs method. // // This restriction is due to the fact that trailing closures in type-checked // argument lists can have an awkward representation. They may be: // - Nested within variadic expansion exprs, and may not be the first element // of that expansion. // - In a position *before* default argument expressions, or even explicitly // written non-trailing arguments in certain backward scanning argument // matching cases. /// Returns the index of the first trailing closure in the argument list, or /// \c None if there are no trailing closures. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. std::optional getFirstTrailingClosureIndex() const { assert(!HasOriginalArgs && "Query original args instead"); if (RawFirstTrailingClosureIndex == NumArgs) return std::nullopt; return RawFirstTrailingClosureIndex; } /// The number of trailing closures in the argument list. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. unsigned getNumTrailingClosures() const { assert(!HasOriginalArgs && "Query original args instead"); if (!getFirstTrailingClosureIndex()) return 0; return size() - *getFirstTrailingClosureIndex(); } /// Whether any unlabeled or labeled trailing closures are present. bool hasAnyTrailingClosures() const { return getOriginalArgs()->getFirstTrailingClosureIndex().has_value(); } /// Whether a given index is for an unlabeled trailing closure, which is the /// first trailing closure in the argument list. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. bool isUnlabeledTrailingClosureIndex(unsigned i) const { assert(!HasOriginalArgs && "Query original args instead"); return hasAnyTrailingClosures() && *getFirstTrailingClosureIndex() == i; } /// Whether a given index is for a labeled trailing closure in an argument /// list with multiple trailing closures. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. bool isLabeledTrailingClosureIndex(unsigned i) const { assert(!HasOriginalArgs && "Query original args instead"); if (!hasAnyTrailingClosures()) return false; return i > *getFirstTrailingClosureIndex() && i < size(); } /// Whether a given index is for a labeled or unlabeled trailing closure. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. bool isTrailingClosureIndex(unsigned i) const { assert(!HasOriginalArgs && "Query original args instead"); if (!hasAnyTrailingClosures()) return false; return i >= *getFirstTrailingClosureIndex() && i < size(); } /// Returns the range of arguments excluding any trailing closures. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. iterator_range getNonTrailingArgs() const { assert(!HasOriginalArgs && "Query original args instead"); auto idx = getFirstTrailingClosureIndex(); if (!idx.has_value()) return *this; return {begin(), iterator(this, *idx)}; } /// Returns the range of trailing closure arguments. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. iterator_range getTrailingClosures() const { assert(!HasOriginalArgs && "Query original args instead"); auto idx = getFirstTrailingClosureIndex(); if (!idx.has_value()) return {end(), end()}; return {iterator(this, *idx), end()}; } /// Retrieve the first trailing closure argument in the argument list, or /// \c None if there are no trailing closures. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. std::optional getFirstTrailingClosure() const { assert(!HasOriginalArgs && "Query original args instead"); auto idx = getFirstTrailingClosureIndex(); if (!idx.has_value()) return std::nullopt; return get(*idx); } /// Retrieve the source range of any trailing closure arguments, or an empty /// range if there are no trailing closures. /// /// Note for a type-checked argument list, this must be queried on /// \c getOriginalArgs instead. SourceRange getTrailingSourceRange() const { assert(!HasOriginalArgs && "Query original args instead"); auto firstClosure = getFirstTrailingClosure(); if (!firstClosure) return SourceRange(); return SourceRange(firstClosure->getStartLoc(), back().getEndLoc()); } // MARK: Misc Queries /// Whether any of the arguments in the argument are inout. bool hasAnyInOutArgs() const { return llvm::any_of(*this, [](auto arg) { return arg.isInOut(); }); } /// Whether the argument list has a single argument, which may be labeled or /// unlabeled. bool isUnary() const { return size() == 1; } /// Whether the argument list has a single unlabeled argument. bool isUnlabeledUnary() const { return isUnary() && getLabel(0).empty(); } /// If the argument list has a single argument, which may be labeled or /// unlabeled, returns its expression, or \c nullptr if this isn't a unary /// argument list. Expr *getUnaryExpr() const { return isUnary() ? getExpr(0) : nullptr; } /// If the argument list has a single unlabeled argument, returns its /// expression, or \c nullptr if this isn't an unlabeled unary argument list. Expr *getUnlabeledUnaryExpr() const { return isUnlabeledUnary() ? getExpr(0) : nullptr; } /// Retrieve an array of the argument expressions used in the argument list. ArrayRef getArgExprs() const { return getExprsBuffer(); } /// Retrieve an array of the argument labels used in the argument list. ArrayRef getArgumentLabels(SmallVectorImpl &scratch) const; /// If the provided expression appears as one of the argument list's /// arguments, returns its index. Otherwise returns \c None. By default this /// will match against semantic sub-expressions, but that may be disabled by /// passing \c false for \c allowSemantic. std::optional findArgumentExpr(Expr *expr, bool allowSemantic = true) const; /// Creates a TupleExpr or ParenExpr that holds the argument exprs. A /// ParenExpr will be returned for a single argument, otherwise a TupleExpr. /// Don't use this function unless you actually need an AST transform. Expr *packIntoImplicitTupleOrParen( ASTContext &ctx, llvm::function_ref getType = __Expr_getType) const; /// Whether the argument list matches a given parameter list. This will return /// \c false if the arity doesn't match, or any of the canonical types or /// labels don't match. Note that this expects types to be present for the /// arguments and parameters. bool matches(ArrayRef params, llvm::function_ref getType = __Expr_getType) const; /// Returns the argument list prior to being rewritten by the expression /// type-checker. If the argument list either hasn't been type-checked yet, /// or was implicitly generated as type-checked, it returns itself. /// /// Note that returned argument list will still have rewritten argument /// expressions with types. However the order of the arguments will reflect /// the call-site as written by the user, default arguments will be stripped, /// and variadic arguments will be expanded. ArgumentList *getOriginalArgs() { if (auto *originalArgs = getStoredOriginalArgs()) return originalArgs; return this; } /// Returns the argument list prior to being rewritten by the expression /// type-checker. If the argument list either hasn't been type-checked yet, /// or was implicitly generated as type-checked, it returns itself. /// /// Note that returned argument list will still have rewritten argument /// expressions with types. However the order of the arguments will reflect /// the call-site as written by the user, default arguments will be stripped, /// and variadic arguments will be expanded. const ArgumentList *getOriginalArgs() const { if (auto *originalArgs = getStoredOriginalArgs()) return originalArgs; return this; } ArgumentList *walk(ASTWalker &walker); SWIFT_DEBUG_DUMP; void dump(raw_ostream &OS, unsigned Indent = 0) const; }; } // end namespace swift #endif