mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
Merge pull request #86842 from aidan-hall/just-func-type-lifetimes-try-print
LifetimeDependence: Support function types
This commit is contained in:
@@ -2574,6 +2574,13 @@ BridgedDifferentiableTypeAttr BridgedDifferentiableTypeAttr_createParsed(
|
||||
swift::SourceLoc nameLoc, swift::SourceRange parensRange,
|
||||
BridgedDifferentiabilityKind cKind, swift::SourceLoc kindLoc);
|
||||
|
||||
SWIFT_NAME("BridgedLifetimeTypeAttr.createParsed(_:atLoc:nameLoc:"
|
||||
"parensRange:entry:)")
|
||||
BridgedLifetimeTypeAttr BridgedLifetimeTypeAttr_createParsed(
|
||||
BridgedASTContext cContext, swift::SourceLoc atLoc,
|
||||
swift::SourceLoc nameLoc, swift::SourceRange parensRange,
|
||||
BridgedLifetimeEntry entry);
|
||||
|
||||
SWIFT_NAME("BridgedIsolatedTypeAttr.createParsed(_:atLoc:nameLoc:parensRange:"
|
||||
"isolationKind:isolationKindLoc:)")
|
||||
BridgedIsolatedTypeAttr BridgedIsolatedTypeAttr_createParsed(
|
||||
|
||||
@@ -368,7 +368,9 @@ public:
|
||||
return printedClangDecl.insert(d).second;
|
||||
}
|
||||
|
||||
void printLifetimeDependence(
|
||||
/// Print lifetimeDependence as a SIL lifetime attribute, attached to a
|
||||
/// parameter or result of a function.
|
||||
void printSILLifetimeDependence(
|
||||
std::optional<LifetimeDependenceInfo> lifetimeDependence) {
|
||||
if (!lifetimeDependence.has_value()) {
|
||||
return;
|
||||
@@ -380,10 +382,15 @@ public:
|
||||
ArrayRef<LifetimeDependenceInfo> lifetimeDependencies, unsigned index) {
|
||||
if (auto lifetimeDependence =
|
||||
getLifetimeDependenceFor(lifetimeDependencies, index)) {
|
||||
printLifetimeDependence(*lifetimeDependence);
|
||||
printSILLifetimeDependence(*lifetimeDependence);
|
||||
}
|
||||
}
|
||||
|
||||
/// Print lifetimeDependence as a Swift lifetime attribute.
|
||||
void
|
||||
printSwiftLifetimeDependence(LifetimeDependenceInfo const &lifetimeDependence,
|
||||
ArrayRef<AnyFunctionType::Param> params);
|
||||
|
||||
private:
|
||||
virtual void anchor();
|
||||
};
|
||||
|
||||
@@ -4274,6 +4274,17 @@ public:
|
||||
|
||||
SWIFT_DEBUG_DUMPER(dump());
|
||||
void print(ASTPrinter &Printer, const PrintOptions &Options) const;
|
||||
|
||||
/// Returns true if multiple instances of an attribute kind
|
||||
/// can appear on a type.
|
||||
static constexpr bool allowMultipleAttributes(TypeAttrKind TK) {
|
||||
switch (TK) {
|
||||
case swift::TypeAttrKind::Lifetime:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AtTypeAttrBase : public TypeAttribute {
|
||||
@@ -4383,6 +4394,19 @@ public:
|
||||
void printImpl(ASTPrinter &printer, const PrintOptions &options) const;
|
||||
};
|
||||
|
||||
class LifetimeTypeAttr : public SimpleTypeAttrWithArgs<TypeAttrKind::Lifetime> {
|
||||
LifetimeEntry *entry;
|
||||
|
||||
public:
|
||||
LifetimeTypeAttr(SourceLoc atLoc, SourceLoc kwLoc, SourceRange parens,
|
||||
LifetimeEntry *entry)
|
||||
: SimpleTypeAttr(atLoc, kwLoc, parens), entry(entry) {}
|
||||
|
||||
LifetimeEntry *getLifetimeEntry() const { return entry; }
|
||||
|
||||
void printImpl(ASTPrinter &printer, const PrintOptions &options) const;
|
||||
};
|
||||
|
||||
class OpaqueReturnTypeOfTypeAttr
|
||||
: public SimpleTypeAttrWithArgs<TypeAttrKind::OpaqueReturnTypeOf> {
|
||||
Located<StringRef> MangledName;
|
||||
|
||||
@@ -8511,6 +8511,8 @@ ERROR(lifetime_target_requires_nonescapable, none,
|
||||
"invalid lifetime dependence on an Escapable %0", (StringRef))
|
||||
NOTE(lifetime_escapable_source_requires_escapable_note, none,
|
||||
"use '@_lifetime(%0%1)' instead", (StringRef, StringRef))
|
||||
ERROR(lifetime_dependence_unknown_type, none,
|
||||
"lifetime dependence checking failed due to unknown %0 type", (StringRef))
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// MARK: Lifetime Dependence Requirements
|
||||
|
||||
@@ -31,8 +31,10 @@
|
||||
namespace swift {
|
||||
|
||||
class AbstractFunctionDecl;
|
||||
class AnyFunctionType;
|
||||
class FunctionTypeRepr;
|
||||
class LifetimeDependentTypeRepr;
|
||||
class LifetimeTypeAttr;
|
||||
class SILParameterInfo;
|
||||
class SILResultInfo;
|
||||
|
||||
@@ -190,23 +192,28 @@ public:
|
||||
class LifetimeDependenceInfo {
|
||||
IndexSubset *inheritLifetimeParamIndices;
|
||||
IndexSubset *scopeLifetimeParamIndices;
|
||||
llvm::PointerIntPair<IndexSubset *, 1, bool>
|
||||
addressableParamIndicesAndImmortal;
|
||||
// The outer bool is the "isFromAnnotation" bit. The inner one is the
|
||||
// "isImmortal" bit.
|
||||
llvm::PointerIntPair<llvm::PointerIntPair<IndexSubset *, 1, bool>, 1, bool>
|
||||
addressableParamIndicesAndImmortalAndFromAnnotation;
|
||||
IndexSubset *conditionallyAddressableParamIndices;
|
||||
|
||||
unsigned targetIndex;
|
||||
|
||||
public:
|
||||
/// Fully-initialized dependence info.
|
||||
LifetimeDependenceInfo(IndexSubset *inheritLifetimeParamIndices,
|
||||
IndexSubset *scopeLifetimeParamIndices,
|
||||
unsigned targetIndex, bool isImmortal,
|
||||
// set during SIL type lowering
|
||||
IndexSubset *addressableParamIndices = nullptr,
|
||||
IndexSubset *conditionallyAddressableParamIndices = nullptr)
|
||||
bool isFromAnnotation,
|
||||
IndexSubset *addressableParamIndices,
|
||||
IndexSubset *conditionallyAddressableParamIndices)
|
||||
: inheritLifetimeParamIndices(inheritLifetimeParamIndices),
|
||||
scopeLifetimeParamIndices(scopeLifetimeParamIndices),
|
||||
addressableParamIndicesAndImmortal(addressableParamIndices, isImmortal),
|
||||
conditionallyAddressableParamIndices(conditionallyAddressableParamIndices),
|
||||
addressableParamIndicesAndImmortalAndFromAnnotation(
|
||||
{addressableParamIndices, isImmortal}, isFromAnnotation),
|
||||
conditionallyAddressableParamIndices(
|
||||
conditionallyAddressableParamIndices),
|
||||
targetIndex(targetIndex) {
|
||||
ASSERT(this->isImmortal() || inheritLifetimeParamIndices ||
|
||||
scopeLifetimeParamIndices);
|
||||
@@ -238,6 +245,18 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// Partially-initialized dependence info, with addressable & conditionally
|
||||
/// addressable parameter indices unset for now.
|
||||
LifetimeDependenceInfo(IndexSubset *inheritLifetimeParamIndices,
|
||||
IndexSubset *scopeLifetimeParamIndices,
|
||||
unsigned targetIndex, bool isImmortal,
|
||||
bool isFromAnnotation)
|
||||
: LifetimeDependenceInfo(inheritLifetimeParamIndices,
|
||||
scopeLifetimeParamIndices, targetIndex,
|
||||
isImmortal, isFromAnnotation,
|
||||
// set during SIL type lowering
|
||||
nullptr, nullptr) {}
|
||||
|
||||
operator bool() const { return !empty(); }
|
||||
|
||||
bool empty() const {
|
||||
@@ -245,7 +264,18 @@ public:
|
||||
scopeLifetimeParamIndices == nullptr;
|
||||
}
|
||||
|
||||
bool isImmortal() const { return addressableParamIndicesAndImmortal.getInt(); }
|
||||
bool isImmortal() const {
|
||||
return addressableParamIndicesAndImmortalAndFromAnnotation.getPointer()
|
||||
.getInt();
|
||||
}
|
||||
|
||||
/// Whether this lifetime dependence corresponds to a @lifetime annotation in
|
||||
/// the source program (Swift or SIL). Such dependencies are likely to differ
|
||||
/// from the default (inferred) ones, so they must be included when printing a
|
||||
/// Swift function's type.
|
||||
bool isFromAnnotation() const {
|
||||
return addressableParamIndicesAndImmortalAndFromAnnotation.getInt();
|
||||
}
|
||||
|
||||
unsigned getTargetIndex() const { return targetIndex; }
|
||||
|
||||
@@ -256,7 +286,8 @@ public:
|
||||
return scopeLifetimeParamIndices != nullptr;
|
||||
}
|
||||
bool hasAddressableParamIndices() const {
|
||||
return addressableParamIndicesAndImmortal.getPointer() != nullptr;
|
||||
return addressableParamIndicesAndImmortalAndFromAnnotation.getPointer()
|
||||
.getPointer() != nullptr;
|
||||
}
|
||||
|
||||
unsigned getParamIndicesLength() const {
|
||||
@@ -282,7 +313,8 @@ public:
|
||||
/// not only on the value, but the memory location of a particular instance
|
||||
/// of the value.
|
||||
IndexSubset *getAddressableIndices() const {
|
||||
return addressableParamIndicesAndImmortal.getPointer();
|
||||
return addressableParamIndicesAndImmortalAndFromAnnotation.getPointer()
|
||||
.getPointer();
|
||||
}
|
||||
/// Return the set of parameters which may have addressable dependencies
|
||||
/// depending on the type of the parameter.
|
||||
@@ -307,6 +339,12 @@ public:
|
||||
&& scopeLifetimeParamIndices->contains(index);
|
||||
}
|
||||
|
||||
/// Get a string representation of this LifetimeDependenceInfo suitable for
|
||||
/// printing in SIL. The target is not included, since this is determined by
|
||||
/// the position of the attribute in SIL.
|
||||
///
|
||||
/// For printing function type lifetimes in Swift, see
|
||||
/// ASTPrinter::printSwiftLifetimeDependence.
|
||||
std::string getString() const;
|
||||
void Profile(llvm::FoldingSetNodeID &ID) const;
|
||||
void getConcatenatedData(SmallVectorImpl<bool> &concatenatedData) const;
|
||||
@@ -321,6 +359,12 @@ public:
|
||||
getFromSIL(FunctionTypeRepr *funcRepr, ArrayRef<SILParameterInfo> params,
|
||||
ArrayRef<SILResultInfo> results, DeclContext *dc);
|
||||
|
||||
/// Builds LifetimeDependenceInfo from a function type.
|
||||
static std::optional<llvm::ArrayRef<LifetimeDependenceInfo>>
|
||||
getFromAST(FunctionTypeRepr *funcRepr, AnyFunctionType *funcType,
|
||||
ArrayRef<LifetimeTypeAttr *> lifetimeAttributes, DeclContext *dc,
|
||||
GenericEnvironment *env);
|
||||
|
||||
bool operator==(const LifetimeDependenceInfo &other) const {
|
||||
return this->isImmortal() == other.isImmortal() &&
|
||||
this->getTargetIndex() == other.getTargetIndex() &&
|
||||
|
||||
@@ -153,7 +153,7 @@ ValueOwnership asValueOwnership(ParameterOwnership o);
|
||||
static inline llvm::StringRef getOwnershipSpelling(ValueOwnership ownership) {
|
||||
switch (ownership) {
|
||||
case ValueOwnership::Default:
|
||||
return "";
|
||||
return "default";
|
||||
case ValueOwnership::InOut:
|
||||
return "inout";
|
||||
case ValueOwnership::Shared:
|
||||
|
||||
@@ -62,6 +62,7 @@ SIMPLE_TYPE_ATTR(reparented, Reparented)
|
||||
SIMPLE_TYPE_ATTR(unchecked, Unchecked)
|
||||
SIMPLE_TYPE_ATTR(preconcurrency, Preconcurrency)
|
||||
SIMPLE_TYPE_ATTR(unsafe, Unsafe)
|
||||
TYPE_ATTR(_lifetime, Lifetime)
|
||||
SIMPLE_TYPE_ATTR(_local, Local)
|
||||
SIMPLE_TYPE_ATTR(_noMetadata, NoMetadata)
|
||||
TYPE_ATTR(_opaqueReturnTypeOf, OpaqueReturnTypeOf)
|
||||
|
||||
@@ -3790,6 +3790,10 @@ public:
|
||||
return Bits.AnyFunctionType.HasLifetimeDependencies;
|
||||
}
|
||||
|
||||
/// Type has lifetime dependencies derived from explicit @_lifetime
|
||||
/// attributes.
|
||||
bool hasExplicitLifetimeDependencies() const;
|
||||
|
||||
ClangTypeInfo getClangTypeInfo() const;
|
||||
ClangTypeInfo getCanonicalClangTypeInfo() const;
|
||||
|
||||
|
||||
@@ -278,6 +278,7 @@ LANGUAGE_FEATURE(InoutLifetimeDependence, 0, "Support @_lifetime(&)")
|
||||
SUPPRESSIBLE_LANGUAGE_FEATURE(NonexhaustiveAttribute, 487, "Nonexhaustive Enums")
|
||||
LANGUAGE_FEATURE(ModuleSelector, 491, "Module selectors (`Module::name` syntax)")
|
||||
LANGUAGE_FEATURE(BuiltinConcurrencyStackNesting, 0, "Concurrency Stack Nesting Builtins")
|
||||
LANGUAGE_FEATURE(ClosureLifetimes, 0, "Support @_lifetime on function types")
|
||||
|
||||
// TEMPORARY: Never use this, because it is only meant to allow us to model
|
||||
// suppression of @c in Swift interfaces as a temporary measure.
|
||||
|
||||
@@ -198,6 +198,7 @@ struct BridgedLifetimeDependenceInfo {
|
||||
swift::IndexSubset *_Nullable conditionallyAddressableParamIndices;
|
||||
SwiftUInt targetIndex;
|
||||
bool immortal;
|
||||
bool fromAnnotation;
|
||||
|
||||
BRIDGED_INLINE BridgedLifetimeDependenceInfo(swift::LifetimeDependenceInfo info);
|
||||
|
||||
|
||||
@@ -171,13 +171,15 @@ BridgedParameterInfo BridgedParameterInfoArray::at(SwiftInt parameterIndex) cons
|
||||
// BridgedLifetimeDependenceInfo
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
BridgedLifetimeDependenceInfo::BridgedLifetimeDependenceInfo(swift::LifetimeDependenceInfo info)
|
||||
BridgedLifetimeDependenceInfo::BridgedLifetimeDependenceInfo(
|
||||
swift::LifetimeDependenceInfo info)
|
||||
: inheritLifetimeParamIndices(info.getInheritIndices()),
|
||||
scopeLifetimeParamIndices(info.getScopeIndices()),
|
||||
addressableParamIndices(info.getAddressableIndices()),
|
||||
conditionallyAddressableParamIndices(
|
||||
info.getConditionallyAddressableIndices()),
|
||||
targetIndex(info.getTargetIndex()), immortal(info.isImmortal()) {}
|
||||
info.getConditionallyAddressableIndices()),
|
||||
targetIndex(info.getTargetIndex()), immortal(info.isImmortal()),
|
||||
fromAnnotation(info.isFromAnnotation()) {}
|
||||
|
||||
SwiftInt BridgedLifetimeDependenceInfoArray::count() const {
|
||||
return lifetimeDependenceInfoArray.unbridged<swift::LifetimeDependenceInfo>().size();
|
||||
|
||||
+164
-17
@@ -504,6 +504,108 @@ bool TypeTransformContext::isPrintingSynthesizedExtension() const {
|
||||
return !Decl.isNull();
|
||||
}
|
||||
|
||||
/// Get a string representation of the parameter at params[index]. This will be
|
||||
/// the label or internal label of a normal parameter, or the string form of
|
||||
/// index. We cannot currently print lifetimes with indices in swiftinterface
|
||||
/// files because SwiftSyntax cannot parse lifetime entries that use them.
|
||||
static std::string
|
||||
getLifetimeDependenceIdentifier(unsigned index,
|
||||
ArrayRef<AnyFunctionType::Param> params) {
|
||||
// TODO: Print "self" for the self parameter. Requires a way to determine if a
|
||||
// type is the interface type of an instance method.
|
||||
if (index < params.size() && params[index].hasInternalLabel()) {
|
||||
return params[index].getInternalLabel().get();
|
||||
}
|
||||
|
||||
return std::to_string(index);
|
||||
}
|
||||
|
||||
/// Get a string representation for the list of sources of the given
|
||||
/// LifetimeDependenceInfo, if they can be printed.
|
||||
///
|
||||
/// See getLifetimeDependenceIdentifier.
|
||||
static std::string getLifetimeDependenceInfoSourceListString(
|
||||
LifetimeDependenceInfo const &info,
|
||||
ArrayRef<AnyFunctionType::Param> params) {
|
||||
|
||||
std::string lifetimeDependenceString = "";
|
||||
auto addressable = info.getAddressableIndices();
|
||||
auto condAddressable = info.getConditionallyAddressableIndices();
|
||||
|
||||
auto getSourceString = [&](IndexSubset *bitvector,
|
||||
StringRef kind) -> std::string {
|
||||
std::string result;
|
||||
bool isFirstSetBit = true;
|
||||
for (unsigned i = 0; i < bitvector->getCapacity(); i++) {
|
||||
if (bitvector->contains(i)) {
|
||||
if (!isFirstSetBit) {
|
||||
result += ", ";
|
||||
}
|
||||
result += kind;
|
||||
if (addressable && addressable->contains(i)) {
|
||||
result += "address ";
|
||||
} else if (condAddressable && condAddressable->contains(i)) {
|
||||
result += "address_for_deps ";
|
||||
}
|
||||
// Print source labels for explicit lifetime annotations. Otherwise,
|
||||
// print the index. Indices should ideally never be used in Swift
|
||||
// lifetime annotations, especially in module interfaces.
|
||||
result += getLifetimeDependenceIdentifier(i, params);
|
||||
isFirstSetBit = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto inheritLifetimeParamIndices = info.getInheritIndices();
|
||||
if (inheritLifetimeParamIndices) {
|
||||
assert(!inheritLifetimeParamIndices->isEmpty());
|
||||
lifetimeDependenceString +=
|
||||
getSourceString(inheritLifetimeParamIndices, "copy ");
|
||||
}
|
||||
if (auto scopeLifetimeParamIndices = info.getScopeIndices()) {
|
||||
assert(!scopeLifetimeParamIndices->isEmpty());
|
||||
if (inheritLifetimeParamIndices) {
|
||||
lifetimeDependenceString += ", ";
|
||||
}
|
||||
lifetimeDependenceString +=
|
||||
getSourceString(scopeLifetimeParamIndices, "borrow ");
|
||||
}
|
||||
if (info.isImmortal()) {
|
||||
lifetimeDependenceString += "immortal";
|
||||
}
|
||||
return lifetimeDependenceString;
|
||||
}
|
||||
|
||||
/// Get a string representation for this dependence info as a Swift lifetime
|
||||
/// attribute (for a type or decl). The target and sources are referred to by
|
||||
/// their labels if possible (see getLifetimeDependenceIdentifier).
|
||||
static std::string
|
||||
getLifetimeDependenceInfoSwiftString(LifetimeDependenceInfo const &info,
|
||||
ArrayRef<AnyFunctionType::Param> params) {
|
||||
std::string lifetimeDependenceString = "@_lifetime(";
|
||||
|
||||
// Only print the target if it is a parameter or self.
|
||||
const auto resultIndex = params.size();
|
||||
const auto targetIndex = info.getTargetIndex();
|
||||
if (targetIndex != resultIndex) {
|
||||
lifetimeDependenceString +=
|
||||
getLifetimeDependenceIdentifier(targetIndex, params);
|
||||
lifetimeDependenceString += ": ";
|
||||
}
|
||||
|
||||
lifetimeDependenceString +=
|
||||
getLifetimeDependenceInfoSourceListString(info, params);
|
||||
lifetimeDependenceString += ") ";
|
||||
return lifetimeDependenceString;
|
||||
}
|
||||
|
||||
void ASTPrinter::printSwiftLifetimeDependence(
|
||||
LifetimeDependenceInfo const &lifetimeDependence,
|
||||
ArrayRef<AnyFunctionType::Param> params) {
|
||||
|
||||
*this << getLifetimeDependenceInfoSwiftString(lifetimeDependence, params);
|
||||
}
|
||||
|
||||
void ASTPrinter::anchor() {}
|
||||
|
||||
void ASTPrinter::printIndent() {
|
||||
@@ -6761,6 +6863,17 @@ public:
|
||||
Printer.printSimpleAttr("@Sendable") << " ";
|
||||
}
|
||||
|
||||
// Print lifetime dependencies using Swift syntax.
|
||||
if (!Options.PrintInSILBody && fnType->hasLifetimeDependencies()) {
|
||||
ArrayRef<AnyFunctionType::Param> params = fnType->getParams();
|
||||
|
||||
for (const auto &lifetimeDependence : info.getLifetimeDependencies()) {
|
||||
if (lifetimeDependence.isFromAnnotation()) {
|
||||
Printer.printSwiftLifetimeDependence(lifetimeDependence, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SmallString<64> buf;
|
||||
switch (Options.PrintFunctionRepresentationAttrs) {
|
||||
case PrintOptions::FunctionRepresentationMode::None:
|
||||
@@ -6946,8 +7059,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// Print a function type's parameter list.
|
||||
///
|
||||
/// * printExternalLabels: Whether to print the parameters' external labels.
|
||||
///
|
||||
/// * printAllLabels: Whether to print all parameter labels (external and
|
||||
/// internal). This is used when a function has explicit lifetime
|
||||
/// dependencies, which may refer to the parameters by their labels.
|
||||
void visitAnyFunctionTypeParams(
|
||||
ArrayRef<AnyFunctionType::Param> Params, bool printLabels,
|
||||
ArrayRef<AnyFunctionType::Param> Params, bool printExternalLabels,
|
||||
bool printAllLabels,
|
||||
ArrayRef<LifetimeDependenceInfo> lifetimeDependencies) {
|
||||
Printer << "(";
|
||||
|
||||
@@ -6961,16 +7082,23 @@ public:
|
||||
Printer.printStructurePost(PrintStructureKind::FunctionParameter);
|
||||
};
|
||||
|
||||
if ((Options.AlwaysTryPrintParameterLabels || printLabels) &&
|
||||
bool hasValidInternalLabel = Param.hasInternalLabel() &&
|
||||
!Param.getInternalLabel().hasDollarPrefix();
|
||||
|
||||
if ((Options.AlwaysTryPrintParameterLabels || printExternalLabels ||
|
||||
printAllLabels) &&
|
||||
Param.hasLabel()) {
|
||||
// Label printing was requested and we have an external label. Print it
|
||||
// and omit the internal label.
|
||||
Printer.printName(Param.getLabel(),
|
||||
PrintNameContext::FunctionParameterExternal);
|
||||
if (printAllLabels && hasValidInternalLabel) {
|
||||
Printer << ' ';
|
||||
Printer.printName(Param.getInternalLabel());
|
||||
}
|
||||
Printer << ": ";
|
||||
} else if (Options.AlwaysTryPrintParameterLabels &&
|
||||
Param.hasInternalLabel() &&
|
||||
!Param.getInternalLabel().hasDollarPrefix()) {
|
||||
} else if ((Options.AlwaysTryPrintParameterLabels || printAllLabels) &&
|
||||
hasValidInternalLabel) {
|
||||
// We didn't have an external parameter label but were requested to
|
||||
// always try and print parameter labels.
|
||||
// If the internal label is a valid internal parameter label (does not
|
||||
@@ -6982,7 +7110,9 @@ public:
|
||||
Printer << ": ";
|
||||
}
|
||||
|
||||
Printer.printLifetimeDependenceAt(lifetimeDependencies, i);
|
||||
if (Options.PrintInSILBody) {
|
||||
Printer.printLifetimeDependenceAt(lifetimeDependencies, i);
|
||||
}
|
||||
|
||||
auto type = Param.getPlainType();
|
||||
if (Param.isVariadic()) {
|
||||
@@ -6998,6 +7128,12 @@ public:
|
||||
Printer << ")";
|
||||
}
|
||||
|
||||
static bool shouldPrintLabelsForLifetimeAttributes(AnyFunctionType const *T) {
|
||||
// If the type has explicit lifetime dependencies, print the labels, because
|
||||
// explicit lifetimes use them to describe their sources and targets.
|
||||
return T->hasExplicitLifetimeDependencies();
|
||||
}
|
||||
|
||||
void visitFunctionType(FunctionType *T,
|
||||
NonRecursivePrintOptions nrOptions) {
|
||||
Printer.callPrintStructurePre(PrintStructureKind::FunctionType);
|
||||
@@ -7008,7 +7144,10 @@ public:
|
||||
printFunctionExtInfo(T, nrOptions);
|
||||
|
||||
// If we're stripping argument labels from types, do it when printing.
|
||||
visitAnyFunctionTypeParams(T->getParams(), /*printLabels*/ false,
|
||||
visitAnyFunctionTypeParams(T->getParams(),
|
||||
/*printExternalLabels*/ false,
|
||||
/*printAllLabels*/
|
||||
shouldPrintLabelsForLifetimeAttributes(T),
|
||||
T->getLifetimeDependencies());
|
||||
|
||||
if (T->hasExtInfo()) {
|
||||
@@ -7034,7 +7173,10 @@ public:
|
||||
Printer.printKeyword("sending ", Options);
|
||||
}
|
||||
|
||||
Printer.printLifetimeDependence(T->getLifetimeDependenceForResult());
|
||||
if (Options.PrintInSILBody) {
|
||||
Printer.printLifetimeDependenceAt(T->getLifetimeDependencies(),
|
||||
T->getParams().size());
|
||||
}
|
||||
|
||||
Printer.callPrintStructurePre(PrintStructureKind::FunctionReturnType);
|
||||
T->getResult().print(Printer, Options);
|
||||
@@ -7069,7 +7211,9 @@ public:
|
||||
PrintAST::defaultGenericRequirementFlags(Options));
|
||||
Printer << " ";
|
||||
|
||||
visitAnyFunctionTypeParams(T->getParams(), /*printLabels*/ true,
|
||||
visitAnyFunctionTypeParams(T->getParams(),
|
||||
/*printExternalLabels*/ true, /*printAllLabels*/
|
||||
shouldPrintLabelsForLifetimeAttributes(T),
|
||||
T->getLifetimeDependencies());
|
||||
|
||||
if (T->hasExtInfo()) {
|
||||
@@ -7091,8 +7235,10 @@ public:
|
||||
|
||||
Printer << " -> ";
|
||||
|
||||
Printer.printLifetimeDependenceAt(T->getLifetimeDependencies(),
|
||||
T->getParams().size());
|
||||
if (Options.PrintInSILBody) {
|
||||
Printer.printLifetimeDependenceAt(T->getLifetimeDependencies(),
|
||||
T->getParams().size());
|
||||
}
|
||||
|
||||
Printer.callPrintStructurePre(PrintStructureKind::FunctionReturnType);
|
||||
T->getResult().print(Printer, Options);
|
||||
@@ -7197,7 +7343,7 @@ public:
|
||||
}
|
||||
Printer << ") -> ";
|
||||
|
||||
Printer.printLifetimeDependence(T->getLifetimeDependenceForResult());
|
||||
Printer.printSILLifetimeDependence(T->getLifetimeDependenceForResult());
|
||||
|
||||
bool parenthesizeResults = mustParenthesizeResults(T);
|
||||
if (parenthesizeResults)
|
||||
@@ -7841,12 +7987,13 @@ void AnyFunctionType::printParams(ArrayRef<AnyFunctionType::Param> Params,
|
||||
printParams(Params, Printer, PO);
|
||||
}
|
||||
void AnyFunctionType::printParams(ArrayRef<AnyFunctionType::Param> Params,
|
||||
ASTPrinter &Printer,
|
||||
const PrintOptions &PO) {
|
||||
// TODO: Handle lifetime dependence printing here
|
||||
ASTPrinter &Printer, const PrintOptions &PO) {
|
||||
// Function type lifetimes are printed as part of the ExtInfo, not the
|
||||
// parameter list, so we do not need to print them here.
|
||||
TypePrinter(Printer, PO)
|
||||
.visitAnyFunctionTypeParams(Params,
|
||||
/*printLabels*/ true, {});
|
||||
/*printExternalLabels*/ true,
|
||||
/*printAllLabels*/ false, {});
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -8035,7 +8182,7 @@ void SILParameterInfo::print(
|
||||
}
|
||||
|
||||
if (lifetimeDependence) {
|
||||
Printer.printLifetimeDependence(*lifetimeDependence);
|
||||
Printer.printSILLifetimeDependence(*lifetimeDependence);
|
||||
}
|
||||
|
||||
if (options.contains(SILParameterInfo::ImplicitLeading)) {
|
||||
|
||||
@@ -238,6 +238,14 @@ void DifferentiableTypeAttr::printImpl(ASTPrinter &printer,
|
||||
printer.printStructurePost(PrintStructureKind::BuiltinAttribute);
|
||||
}
|
||||
|
||||
void LifetimeTypeAttr::printImpl(ASTPrinter &printer,
|
||||
const PrintOptions &options) const {
|
||||
printer.callPrintStructurePre(PrintStructureKind::BuiltinAttribute);
|
||||
printer.printAttrName("@_lifetime");
|
||||
printer << entry->getString();
|
||||
printer.printStructurePost(PrintStructureKind::BuiltinAttribute);
|
||||
}
|
||||
|
||||
void ConventionTypeAttr::printImpl(ASTPrinter &printer,
|
||||
const PrintOptions &options) const {
|
||||
printer.callPrintStructurePre(PrintStructureKind::BuiltinAttribute);
|
||||
|
||||
@@ -69,6 +69,14 @@ BridgedDifferentiableTypeAttr BridgedDifferentiableTypeAttr_createParsed(
|
||||
atLoc, nameLoc, parensRange, {unbridged(cKind), kindLoc});
|
||||
}
|
||||
|
||||
BridgedLifetimeTypeAttr BridgedLifetimeTypeAttr_createParsed(
|
||||
BridgedASTContext cContext, swift::SourceLoc atLoc,
|
||||
swift::SourceLoc nameLoc, swift::SourceRange parensRange,
|
||||
BridgedLifetimeEntry entry) {
|
||||
return new (cContext.unbridged())
|
||||
LifetimeTypeAttr(atLoc, nameLoc, parensRange, entry.unbridged());
|
||||
}
|
||||
|
||||
BridgedIsolatedTypeAttr BridgedIsolatedTypeAttr_createParsed(
|
||||
BridgedASTContext cContext, SourceLoc atLoc, SourceLoc nameLoc,
|
||||
SourceRange parensRange, BridgedIsolatedTypeAttrIsolationKind cIsolation,
|
||||
|
||||
@@ -233,6 +233,56 @@ static bool usesFeatureLifetimes(Decl *decl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hasLifetimeDependencies(Type type) {
|
||||
if (auto *aft = type->getAs<AnyFunctionType>()) {
|
||||
return aft->hasExplicitLifetimeDependencies();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Search for any types within decl with lifetime dependencies. Ignore
|
||||
/// lifetimes on AbstractFunctionDecl decls, since those are supported by the
|
||||
/// Lifetimes feature, not ClosureLifetimes.
|
||||
static bool findClosureLifetimes(Decl *decl) {
|
||||
// Search for any Decl that uses a type with lifetime dependencies, possibly
|
||||
// restricting this to explicit dependencies.
|
||||
class ClosureLifetimesWalker : public ASTWalker {
|
||||
|
||||
public:
|
||||
bool useFound = false;
|
||||
|
||||
PreWalkAction walkToDeclPre(Decl *D) override {
|
||||
if (auto *afd = dyn_cast<AbstractFunctionDecl>(D)) {
|
||||
// Check the parameters and result, but not the AFD itself, since
|
||||
// lifetimes on AFDs are supported by the Lifetimes feature.
|
||||
Type resultType =
|
||||
afd->getInterfaceType()->getAs<AnyFunctionType>()->getResult();
|
||||
useFound = resultType.findIf(hasLifetimeDependencies);
|
||||
return Action::StopIf(useFound);
|
||||
}
|
||||
|
||||
// Check for lifetime dependence info on param and type decls.
|
||||
if (isa<ParamDecl>(D) || isa<TypeDecl>(D)) {
|
||||
useFound = usesTypeMatching(D, hasLifetimeDependencies);
|
||||
return Action::StopIf(useFound);
|
||||
}
|
||||
|
||||
// Any other Decl kinds are irrelevant.
|
||||
return Action::SkipChildren();
|
||||
}
|
||||
};
|
||||
|
||||
ClosureLifetimesWalker walker;
|
||||
decl->walk(walker);
|
||||
return walker.useFound;
|
||||
}
|
||||
|
||||
static bool usesFeatureClosureLifetimes(Decl *decl) {
|
||||
// This will find function types with lifetimes & closures with lifetimes,
|
||||
// since it walks the AST rooted at decl, checking types.
|
||||
return findClosureLifetimes(decl);
|
||||
}
|
||||
|
||||
static bool usesFeatureInoutLifetimeDependence(Decl *decl) {
|
||||
auto hasInoutLifetimeDependence = [](Decl *decl) {
|
||||
for (auto attr : decl->getAttrs().getAttributes<LifetimeAttr>()) {
|
||||
|
||||
+175
-64
@@ -17,6 +17,7 @@
|
||||
#include "swift/AST/ConformanceLookup.h"
|
||||
#include "swift/AST/Decl.h"
|
||||
#include "swift/AST/DiagnosticsSema.h"
|
||||
#include "swift/AST/GenericEnvironment.h"
|
||||
#include "swift/AST/Module.h"
|
||||
#include "swift/AST/ParameterList.h"
|
||||
#include "swift/AST/SourceFile.h"
|
||||
@@ -32,6 +33,20 @@
|
||||
|
||||
using namespace swift;
|
||||
|
||||
/// Determine whether Type t is "unknown", meaning we cannot safely determine
|
||||
/// whether it is Escapable by calling TypeBase::isEscapable.
|
||||
static bool isTypeUnknown(Type t) {
|
||||
// These types would hit an assertion in
|
||||
// TypeBase::computeInvertibleConformances.
|
||||
if (t->hasUnboundGenericType() || t->hasTypeParameter())
|
||||
return true;
|
||||
// This type would hit an assertion in checkRequirements.
|
||||
if (t->hasTypeVariable())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LifetimeDescriptor::getString() const {
|
||||
switch (kind) {
|
||||
case DescriptorKind::Named: {
|
||||
@@ -194,7 +209,8 @@ std::string LifetimeDependenceInfo::getString() const {
|
||||
}
|
||||
|
||||
void LifetimeDependenceInfo::Profile(llvm::FoldingSetNodeID &ID) const {
|
||||
ID.AddBoolean(addressableParamIndicesAndImmortal.getInt());
|
||||
ID.AddBoolean(isImmortal());
|
||||
ID.AddBoolean(isFromAnnotation());
|
||||
ID.AddInteger(targetIndex);
|
||||
if (inheritLifetimeParamIndices) {
|
||||
ID.AddInteger((uint8_t)LifetimeDependenceKind::Inherit);
|
||||
@@ -204,11 +220,11 @@ void LifetimeDependenceInfo::Profile(llvm::FoldingSetNodeID &ID) const {
|
||||
ID.AddInteger((uint8_t)LifetimeDependenceKind::Scope);
|
||||
scopeLifetimeParamIndices->Profile(ID);
|
||||
}
|
||||
if (addressableParamIndicesAndImmortal.getPointer()) {
|
||||
if (hasAddressableParamIndices()) {
|
||||
ID.AddBoolean(true);
|
||||
addressableParamIndicesAndImmortal.getPointer()->Profile(ID);
|
||||
getAddressableIndices()->Profile(ID);
|
||||
} else {
|
||||
ID.AddBoolean(false);
|
||||
ID.AddBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +251,7 @@ void LifetimeDependenceInfo::getConcatenatedData(
|
||||
pushData(scopeLifetimeParamIndices);
|
||||
}
|
||||
if (hasAddressableParamIndices()) {
|
||||
pushData(addressableParamIndicesAndImmortal.getPointer());
|
||||
pushData(getAddressableIndices());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,8 +353,8 @@ public:
|
||||
}
|
||||
|
||||
TargetDeps *createAnnotatedTargetDeps(unsigned targetIndex) {
|
||||
auto iterAndInserted = depsArray.try_emplace(targetIndex, true,
|
||||
sourceIndexCap);
|
||||
auto iterAndInserted =
|
||||
depsArray.try_emplace(targetIndex, true, sourceIndexCap);
|
||||
if (!iterAndInserted.second)
|
||||
return nullptr;
|
||||
|
||||
@@ -394,10 +410,11 @@ public:
|
||||
ASSERT(!deps.isImmortal
|
||||
&& "cannot combine immortal lifetime with parameter dependency");
|
||||
}
|
||||
lifetimeDependencies.push_back(LifetimeDependenceInfo{
|
||||
lifetimeDependencies.push_back(LifetimeDependenceInfo(
|
||||
/*inheritLifetimeParamIndices*/ inheritIndices,
|
||||
/*scopeLifetimeParamIndices*/ scopeIndices, targetIndex,
|
||||
/*isImmortal*/ deps.isImmortal});
|
||||
/*isImmortal*/ deps.isImmortal,
|
||||
/*isFromAnnotation*/ deps.hasAnnotation));
|
||||
}
|
||||
if (lifetimeDependencies.empty()) {
|
||||
return std::nullopt;
|
||||
@@ -452,7 +469,7 @@ class LifetimeDependenceChecker {
|
||||
|
||||
// A parameter corresponding to the implicit self declaration of
|
||||
// the function, if it has one. Otherwise, std::nullopt.
|
||||
std::optional<ParamInfo> implicitSelfParam;
|
||||
std::optional<ParamInfo> implicitSelfParamInfo;
|
||||
|
||||
LifetimeDependenceBuilder depBuilder;
|
||||
|
||||
@@ -506,8 +523,8 @@ public:
|
||||
}
|
||||
|
||||
const ParamInfo &getParamForIndex(unsigned paramIndex) {
|
||||
if (implicitSelfParam && paramIndex == implicitSelfParam->index)
|
||||
return *implicitSelfParam;
|
||||
if (implicitSelfParamInfo && paramIndex == implicitSelfParamInfo->index)
|
||||
return *implicitSelfParamInfo;
|
||||
|
||||
assert(paramIndex < parameterInfos.size() && "unexpected result index");
|
||||
return parameterInfos[paramIndex];
|
||||
@@ -517,8 +534,9 @@ public:
|
||||
if (paramOrResultIndex == resultIndex)
|
||||
return resultInterfaceType;
|
||||
|
||||
if (implicitSelfParam && paramOrResultIndex == implicitSelfParam->index)
|
||||
return implicitSelfParam->getInterfaceType();
|
||||
if (implicitSelfParamInfo &&
|
||||
paramOrResultIndex == implicitSelfParamInfo->index)
|
||||
return implicitSelfParamInfo->getInterfaceType();
|
||||
|
||||
return parameterInfos[paramOrResultIndex].getInterfaceType();
|
||||
}
|
||||
@@ -527,8 +545,9 @@ public:
|
||||
if (paramOrResultIndex == resultIndex)
|
||||
return resultTy;
|
||||
|
||||
if (implicitSelfParam && paramOrResultIndex == implicitSelfParam->index)
|
||||
return implicitSelfParam->typeInContext;
|
||||
if (implicitSelfParamInfo &&
|
||||
paramOrResultIndex == implicitSelfParamInfo->index)
|
||||
return implicitSelfParamInfo->typeInContext;
|
||||
|
||||
return parameterInfos[paramOrResultIndex].typeInContext;
|
||||
}
|
||||
@@ -557,31 +576,107 @@ public:
|
||||
LifetimeDependenceChecker(AbstractFunctionDecl *afd)
|
||||
: lifetimeEntries(collectLifetimeEntries(afd->getAttrs())),
|
||||
parameterInfos(collectParameterInfo(afd->getParameters(), afd)),
|
||||
afd(afd),
|
||||
ctx(afd->getDeclContext()->getASTContext()),
|
||||
afd(afd), ctx(afd->getDeclContext()->getASTContext()),
|
||||
sourceFile(afd->getParentSourceFile()),
|
||||
escapableDecl(
|
||||
ctx.getProtocol(
|
||||
escapableDecl(ctx.getProtocol(
|
||||
swift::getKnownProtocolKind(InvertibleProtocolKind::Escapable))),
|
||||
genericEnv(afd->getGenericEnvironment()),
|
||||
resultIndex(getResultIndex(afd)),
|
||||
resultInterfaceType(getResultOrYieldInterface(afd)),
|
||||
resultTy(afd->mapTypeIntoEnvironment(resultInterfaceType)),
|
||||
returnLoc(getReturnLoc(afd)),
|
||||
implicitSelfParam(getSelfParamInfo(afd)),
|
||||
implicitSelfParamInfo(getSelfParamInfo(afd)),
|
||||
depBuilder(/*sourceIndexCap*/ resultIndex),
|
||||
isImplicit(afd->isImplicit()),
|
||||
isInit(isa<ConstructorDecl>(afd)),
|
||||
isImplicit(afd->isImplicit()), isInit(isa<ConstructorDecl>(afd)),
|
||||
hasUnsafeNonEscapableResult(
|
||||
afd->getAttrs().hasAttribute<UnsafeNonEscapableResultAttr>()) {}
|
||||
afd->getAttrs().hasAttribute<UnsafeNonEscapableResultAttr>()) {}
|
||||
|
||||
LifetimeDependenceChecker(FunctionTypeRepr *funcRepr,
|
||||
AnyFunctionType *funcType,
|
||||
ArrayRef<LifetimeTypeAttr *> lifetimeAttrs,
|
||||
DeclContext *dc, GenericEnvironment *env)
|
||||
: afd(nullptr), ctx(funcType->getASTContext()),
|
||||
sourceFile(dc->getParentSourceFile()),
|
||||
resultIndex(funcType->getParams().size()),
|
||||
returnLoc(funcRepr->getResultTypeRepr()->getLoc()),
|
||||
implicitSelfParamInfo(std::nullopt), depBuilder(resultIndex),
|
||||
isImplicit(false), isInit(false), hasUnsafeNonEscapableResult(false) {
|
||||
for (auto functionLifetimeEntry : lifetimeAttrs)
|
||||
lifetimeEntries.push_back(functionLifetimeEntry->getLifetimeEntry());
|
||||
|
||||
// We only ever use the second names of function type parameters for
|
||||
// lifetimes.
|
||||
auto const params = funcType->getParams();
|
||||
auto const argReprs = funcRepr->getArgsTypeRepr()->getElements();
|
||||
|
||||
resultTy =
|
||||
GenericEnvironment::mapTypeIntoEnvironment(env, funcType->getResult());
|
||||
|
||||
assert(params.size() == argReprs.size());
|
||||
for (unsigned paramIndex = 0; paramIndex < params.size(); ++paramIndex) {
|
||||
auto const ¶meter = params[paramIndex];
|
||||
auto const &arg = argReprs[paramIndex];
|
||||
parameterInfos.push_back(
|
||||
{parameter, paramIndex,
|
||||
// If an argument has no second name, use the location of its type.
|
||||
arg.SecondNameLoc.isValid() ? arg.SecondNameLoc : arg.Type->getLoc(),
|
||||
GenericEnvironment::mapTypeIntoEnvironment(
|
||||
env, parameter.getPlainType())});
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<llvm::ArrayRef<LifetimeDependenceInfo>>
|
||||
currentDependencies() const {
|
||||
return depBuilder.initializeDependenceInfoArray(ctx);
|
||||
}
|
||||
|
||||
/// Perform lifetime dependence checks for a function type.
|
||||
std::optional<llvm::ArrayRef<LifetimeDependenceInfo>> checkFuncType() {
|
||||
// Check if the function type contains any types for which we cannot
|
||||
// determine Escapability. We cannot perform lifetime inference for such
|
||||
// types, or check the correctness of lifetime annotations on them, so bail
|
||||
// out. Emit diagnostics if there are any lifetime attributes.
|
||||
//
|
||||
// We could make this more granular, only bailing out if a lifetime source
|
||||
// or target contains an unbound generic, but these cases seem too niche to
|
||||
// be worth the effort.
|
||||
//
|
||||
// Even if there were no explicit lifetime entries, we still need to
|
||||
// diagnose failed inference if a parameter or the result was ~Escapable.
|
||||
const auto isNonEscapableSafe = [](Type t) {
|
||||
return !isTypeUnknown(t) && isDiagnosedNonEscapable(t);
|
||||
};
|
||||
const bool shouldDiagnose =
|
||||
!lifetimeEntries.empty() ||
|
||||
llvm::any_of(parameterInfos,
|
||||
[&](const ParamInfo ¶mInfo) {
|
||||
return isNonEscapableSafe(paramInfo.typeInContext);
|
||||
}) ||
|
||||
isNonEscapableSafe(resultTy);
|
||||
bool unknownTypeFound = false;
|
||||
for (const auto ¶mInfo : parameterInfos) {
|
||||
if (isTypeUnknown(paramInfo.typeInContext)) {
|
||||
unknownTypeFound = true;
|
||||
if (shouldDiagnose)
|
||||
diagnose(paramInfo.loc, diag::lifetime_dependence_unknown_type,
|
||||
"parameter");
|
||||
}
|
||||
}
|
||||
if (isTypeUnknown(resultTy)) {
|
||||
unknownTypeFound = true;
|
||||
if (shouldDiagnose)
|
||||
diagnose(returnLoc, diag::lifetime_dependence_unknown_type, "result");
|
||||
}
|
||||
|
||||
if (unknownTypeFound)
|
||||
return std::nullopt;
|
||||
|
||||
return checkCommon();
|
||||
}
|
||||
|
||||
/// Perform lifetime dependence checks for a function declaration.
|
||||
std::optional<llvm::ArrayRef<LifetimeDependenceInfo>> checkFuncDecl() {
|
||||
assert(isa<FuncDecl>(afd) || isa<ConstructorDecl>(afd));
|
||||
assert(nullptr != afd && (isa<FuncDecl>(afd) || isa<ConstructorDecl>(afd)));
|
||||
assert(depBuilder.empty());
|
||||
|
||||
// Handle Builtins first because, even though Builtins require
|
||||
@@ -592,6 +687,10 @@ public:
|
||||
return currentDependencies();
|
||||
}
|
||||
|
||||
return checkCommon();
|
||||
}
|
||||
|
||||
std::optional<llvm::ArrayRef<LifetimeDependenceInfo>> checkCommon() {
|
||||
if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence)
|
||||
&& !ctx.LangOpts.hasFeature(Feature::Lifetimes)
|
||||
&& !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) {
|
||||
@@ -695,7 +794,7 @@ protected:
|
||||
// the extra formal self parameter, a dependency targeting the formal result
|
||||
// index would incorrectly target the SIL metatype parameter.
|
||||
bool hasImplicitSelfParam() const {
|
||||
return !isInit && implicitSelfParam.has_value();
|
||||
return !isInit && implicitSelfParamInfo.has_value();
|
||||
}
|
||||
|
||||
// In SIL, implicit initializers and accessors become explicit.
|
||||
@@ -746,11 +845,11 @@ protected:
|
||||
return qualifier;
|
||||
}
|
||||
}
|
||||
if (implicitSelfParam.has_value()) {
|
||||
if (implicitSelfParamInfo.has_value()) {
|
||||
if (isInit) {
|
||||
return "an initializer";
|
||||
}
|
||||
if (implicitSelfParam->param.isInOut()) {
|
||||
if (implicitSelfParamInfo->param.isInOut()) {
|
||||
return "a mutating method";
|
||||
}
|
||||
return "a method";
|
||||
@@ -779,14 +878,14 @@ protected:
|
||||
if (!hasImplicitSelfParam()) {
|
||||
return;
|
||||
}
|
||||
if (!implicitSelfParam->param.isInOut()) {
|
||||
if (!implicitSelfParamInfo->param.isInOut()) {
|
||||
return;
|
||||
}
|
||||
if (!isDiagnosedNonEscapable(implicitSelfParam->typeInContext)) {
|
||||
if (!isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext)) {
|
||||
return;
|
||||
}
|
||||
if (!depBuilder.hasTargetDeps(implicitSelfParam->index)) {
|
||||
ctx.Diags.diagnose(implicitSelfParam->loc, diagID,
|
||||
if (!depBuilder.hasTargetDeps(implicitSelfParamInfo->index)) {
|
||||
ctx.Diags.diagnose(implicitSelfParamInfo->loc, diagID,
|
||||
{StringRef(diagnosticQualifier())});
|
||||
}
|
||||
}
|
||||
@@ -846,13 +945,13 @@ protected:
|
||||
|
||||
// Inferrence helper.
|
||||
bool isCompatibleWithOwnership(LifetimeDependenceKind kind,
|
||||
ParamInfo const ¶m) const {
|
||||
ParamInfo const ¶mInfo) const {
|
||||
if (kind == LifetimeDependenceKind::Inherit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto paramType = param.typeInContext;
|
||||
auto loweredOwnership = getLoweredOwnership(param.param);
|
||||
auto paramType = paramInfo.typeInContext;
|
||||
auto loweredOwnership = getLoweredOwnership(paramInfo.param);
|
||||
// Lifetime dependence always propagates through temporary BitwiseCopyable
|
||||
// values, even if the dependence is scoped.
|
||||
if (isBitwiseCopyable(paramType, ctx)) {
|
||||
@@ -1007,7 +1106,7 @@ protected:
|
||||
diag::lifetime_dependence_invalid_self_in_init);
|
||||
return nullptr;
|
||||
}
|
||||
return &implicitSelfParam.value();
|
||||
return &implicitSelfParamInfo.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1054,17 +1153,17 @@ protected:
|
||||
|
||||
// Get the value ownership of param if it is non-default. Otherwise, compute
|
||||
// the lowered value ownership. The supplied Param must be a member of
|
||||
// parameters or implicitSelfParam.value().
|
||||
// parameters or implicitSelfParamInfo.value().
|
||||
ValueOwnership getLoweredOwnership(Param const ¶m) const {
|
||||
auto const ownership = param.getValueOwnership();
|
||||
if (ownership != ValueOwnership::Default)
|
||||
return ownership;
|
||||
if (isa<ConstructorDecl>(afd)) {
|
||||
if (nullptr != afd && isa<ConstructorDecl>(afd)) {
|
||||
return ValueOwnership::Owned;
|
||||
}
|
||||
if (auto *ad = dyn_cast_or_null<AccessorDecl>(afd)) {
|
||||
auto const isSelfParameter = implicitSelfParam.has_value() &&
|
||||
¶m == &(implicitSelfParam->param);
|
||||
auto const isSelfParameter = implicitSelfParamInfo.has_value() &&
|
||||
¶m == &(implicitSelfParamInfo->param);
|
||||
if (ad->getAccessorKind() == AccessorKind::Set) {
|
||||
return isSelfParameter ? ValueOwnership::InOut : ValueOwnership::Owned;
|
||||
}
|
||||
@@ -1224,9 +1323,10 @@ protected:
|
||||
|
||||
// Ignore mutating self. An 'inout' modifier effectively makes the parameter
|
||||
// a different type for lifetime inference.
|
||||
if (hasImplicitSelfParam() && !implicitSelfParam->param.isInOut()) {
|
||||
if (implicitSelfParam->typeInContext->getCanonicalType() == canResultTy) {
|
||||
targetDeps->inheritIndices.set(implicitSelfParam->index);
|
||||
if (hasImplicitSelfParam() && !implicitSelfParamInfo->param.isInOut()) {
|
||||
if (implicitSelfParamInfo->typeInContext->getCanonicalType() ==
|
||||
canResultTy) {
|
||||
targetDeps->inheritIndices.set(implicitSelfParamInfo->index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1259,7 +1359,7 @@ protected:
|
||||
return;
|
||||
}
|
||||
bool nonEscapableSelf =
|
||||
isDiagnosedNonEscapable(implicitSelfParam->typeInContext);
|
||||
isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext);
|
||||
if (nonEscapableSelf && accessor->getImplicitSelfDecl()->isInOut()) {
|
||||
// First, infer the dependency of the inout non-Escapable 'self'. This may
|
||||
// result in two inferred dependencies for accessors (one targetting
|
||||
@@ -1273,7 +1373,7 @@ protected:
|
||||
// Infer the result dependency of the result or yielded value on 'self'
|
||||
// based on the kind of accessor called by this wrapper accessor.
|
||||
if (auto dependenceKind = getImplicitAccessorResultDependence(accessor)) {
|
||||
depBuilder.inferDependency(resultIndex, implicitSelfParam->index,
|
||||
depBuilder.inferDependency(resultIndex, implicitSelfParamInfo->index,
|
||||
*dependenceKind);
|
||||
}
|
||||
}
|
||||
@@ -1302,7 +1402,7 @@ protected:
|
||||
if (paramTypeInContext->hasError()) {
|
||||
return;
|
||||
}
|
||||
depBuilder.inferInoutDependency(implicitSelfParam->index);
|
||||
depBuilder.inferInoutDependency(implicitSelfParamInfo->index);
|
||||
|
||||
// The 'newValue' dependence kind must match the getter's dependence kind
|
||||
// because the generated '_modify' accessor composes the getter's result
|
||||
@@ -1310,7 +1410,7 @@ protected:
|
||||
// Escapable then the getter does not have any lifetime dependency, so the
|
||||
// setter cannot depend on 'newValue'.
|
||||
if (!paramTypeInContext->isEscapable()) {
|
||||
depBuilder.inferDependency(implicitSelfParam->index, newValIdx,
|
||||
depBuilder.inferDependency(implicitSelfParamInfo->index, newValIdx,
|
||||
LifetimeDependenceKind::Inherit);
|
||||
}
|
||||
break;
|
||||
@@ -1322,7 +1422,7 @@ protected:
|
||||
// is the only useful dependence (a borrow of self is possible but not
|
||||
// useful), explicit annotation is required for now to confirm that the
|
||||
// mutated self cannot depend on anything stored at this address.
|
||||
depBuilder.inferInoutDependency(implicitSelfParam->index);
|
||||
depBuilder.inferInoutDependency(implicitSelfParamInfo->index);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1367,9 +1467,9 @@ protected:
|
||||
for (auto &dep : *deps) {
|
||||
if (dep.getTargetIndex() != resultIndex)
|
||||
continue;
|
||||
if (dep.checkInherit(implicitSelfParam->index))
|
||||
if (dep.checkInherit(implicitSelfParamInfo->index))
|
||||
return LifetimeDependenceKind::Inherit;
|
||||
if (dep.checkScope(implicitSelfParam->index))
|
||||
if (dep.checkScope(implicitSelfParamInfo->index))
|
||||
return LifetimeDependenceKind::Scope;
|
||||
}
|
||||
}
|
||||
@@ -1378,7 +1478,7 @@ protected:
|
||||
}
|
||||
// Either a Get or Modify without any wrapped accessor. Handle these like a
|
||||
// read of the stored property.
|
||||
return inferLifetimeDependenceKind(*implicitSelfParam);
|
||||
return inferLifetimeDependenceKind(*implicitSelfParamInfo);
|
||||
}
|
||||
|
||||
// Infer implicit initialization. A non-Escapable initializer parameter can
|
||||
@@ -1445,26 +1545,26 @@ protected:
|
||||
return; // same-type inferrence applied; don't issue diagnostics.
|
||||
|
||||
bool nonEscapableSelf =
|
||||
isDiagnosedNonEscapable(implicitSelfParam->typeInContext);
|
||||
isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext);
|
||||
// Do not infer the result's dependence when the method is mutating and
|
||||
// 'self' is non-Escapable. Independently, a missing dependence on inout
|
||||
// 'self' will be diagnosed. Since an explicit annotation will be needed for
|
||||
// 'self', we also require the method's result to have an explicit
|
||||
// annotation.
|
||||
if (nonEscapableSelf && implicitSelfParam->param.isInOut()) {
|
||||
if (nonEscapableSelf && implicitSelfParamInfo->param.isInOut()) {
|
||||
return;
|
||||
}
|
||||
// Methods with parameters only apply to lazy inference. This does not
|
||||
// include accessors because a subscript's index is assumed not to be the
|
||||
// source of the result's dependency.
|
||||
if (!isa<AccessorDecl>(afd) && !useLazyInference() &&
|
||||
if (!(nullptr != afd && isa<AccessorDecl>(afd)) && !useLazyInference() &&
|
||||
parameterInfos.size() > 0) {
|
||||
return;
|
||||
}
|
||||
if (!useLazyInference() && !isImplicitOrSIL()) {
|
||||
// Require explicit @_lifetime(borrow self) for UnsafePointer-like self.
|
||||
if (!nonEscapableSelf &&
|
||||
isBitwiseCopyable(implicitSelfParam->typeInContext, ctx)) {
|
||||
isBitwiseCopyable(implicitSelfParamInfo->typeInContext, ctx)) {
|
||||
diagnose(returnLoc,
|
||||
diag::lifetime_dependence_cannot_infer_bitwisecopyable,
|
||||
diagnosticQualifier(), "self");
|
||||
@@ -1479,7 +1579,7 @@ protected:
|
||||
}
|
||||
// Infer based on ownership if possible for either explicit accessors or
|
||||
// methods as long as they pass preceding ambiguity checks.
|
||||
auto kind = inferLifetimeDependenceKind(*implicitSelfParam);
|
||||
auto kind = inferLifetimeDependenceKind(*implicitSelfParamInfo);
|
||||
if (!kind) {
|
||||
// Special diagnostic for an attempt to depend on a consuming parameter.
|
||||
diagnose(returnLoc,
|
||||
@@ -1487,7 +1587,7 @@ protected:
|
||||
"self", diagnosticQualifier());
|
||||
return;
|
||||
}
|
||||
resultDeps->addIfNew(implicitSelfParam->index, *kind);
|
||||
resultDeps->addIfNew(implicitSelfParamInfo->index, *kind);
|
||||
}
|
||||
|
||||
// Infer result dependence on a function or intitializer parameter.
|
||||
@@ -1602,15 +1702,15 @@ protected:
|
||||
if (!hasImplicitSelfParam())
|
||||
return;
|
||||
|
||||
if (!isDiagnosedNonEscapable(implicitSelfParam->typeInContext))
|
||||
if (!isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext))
|
||||
return;
|
||||
|
||||
assert(!isInit && "class initializers have Escapable self");
|
||||
if (!implicitSelfParam->param.isInOut())
|
||||
if (!implicitSelfParamInfo->param.isInOut())
|
||||
return;
|
||||
|
||||
// Assume that a mutating method does not depend on its parameters.
|
||||
depBuilder.inferInoutDependency(implicitSelfParam->index);
|
||||
depBuilder.inferInoutDependency(implicitSelfParamInfo->index);
|
||||
}
|
||||
|
||||
// Infer @_lifetime(param: copy param) for 'inout' non-Escapable parameters.
|
||||
@@ -1714,6 +1814,16 @@ LifetimeDependenceInfo::get(ValueDecl *decl) {
|
||||
return LifetimeDependenceChecker::checkEnumElementDecl(eed);
|
||||
}
|
||||
|
||||
std::optional<llvm::ArrayRef<LifetimeDependenceInfo>>
|
||||
LifetimeDependenceInfo::getFromAST(
|
||||
FunctionTypeRepr *funcRepr, AnyFunctionType *funcType,
|
||||
ArrayRef<LifetimeTypeAttr *> lifetimeAttributes, DeclContext *dc,
|
||||
GenericEnvironment *env) {
|
||||
return LifetimeDependenceChecker(funcRepr, funcType, lifetimeAttributes, dc,
|
||||
env)
|
||||
.checkFuncType();
|
||||
}
|
||||
|
||||
void LifetimeDependenceInfo::dump() const {
|
||||
llvm::errs() << "target: " << getTargetIndex() << '\n';
|
||||
if (isImmortal()) {
|
||||
@@ -1809,10 +1919,10 @@ static std::optional<LifetimeDependenceInfo> checkSILTypeModifiers(
|
||||
}
|
||||
case LifetimeDescriptor::DescriptorKind::Named: {
|
||||
assert(source.isImmortal());
|
||||
return LifetimeDependenceInfo(/*inheritLifetimeParamIndices*/ nullptr,
|
||||
/*scopeLifetimeParamIndices*/ nullptr,
|
||||
targetIndex,
|
||||
/*isImmortal*/ true);
|
||||
return LifetimeDependenceInfo(
|
||||
/*inheritLifetimeParamIndices*/ nullptr,
|
||||
/*scopeLifetimeParamIndices*/ nullptr, targetIndex,
|
||||
/*isImmortal*/ true, /*isFromAnnotation*/ true);
|
||||
}
|
||||
default:
|
||||
llvm_unreachable("SIL can only have ordered or immortal lifetime "
|
||||
@@ -1829,6 +1939,7 @@ static std::optional<LifetimeDependenceInfo> checkSILTypeModifiers(
|
||||
: nullptr,
|
||||
targetIndex,
|
||||
/*isImmortal*/ false,
|
||||
/*isFromAnnotation*/ true,
|
||||
addressableLifetimeParamIndices.any()
|
||||
? IndexSubset::get(ctx, addressableLifetimeParamIndices)
|
||||
: nullptr,
|
||||
|
||||
@@ -4218,6 +4218,14 @@ CanType ProtocolCompositionType::getMinimalCanonicalType() const {
|
||||
return result.subst(existentialSig.Generalization)->getCanonicalType();
|
||||
}
|
||||
|
||||
bool AnyFunctionType::hasExplicitLifetimeDependencies() const {
|
||||
return hasLifetimeDependencies() &&
|
||||
llvm::any_of(getLifetimeDependencies(),
|
||||
[](const LifetimeDependenceInfo &dep) {
|
||||
return dep.isFromAnnotation();
|
||||
});
|
||||
}
|
||||
|
||||
ClangTypeInfo AnyFunctionType::getClangTypeInfo() const {
|
||||
switch (getKind()) {
|
||||
case TypeKind::Function:
|
||||
|
||||
@@ -2769,6 +2769,7 @@ void LifetimeDependenceInfoRequest::cacheResult(
|
||||
}
|
||||
auto *eed = cast<EnumElementDecl>(decl);
|
||||
eed->LazySemanticInfo.NoLifetimeDependenceInfo = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
decl->getASTContext().evaluator.cacheNonEmptyOutput(*this, std::move(result));
|
||||
|
||||
@@ -85,6 +85,9 @@ extension ASTGenVisitor {
|
||||
case .Differentiable:
|
||||
return (self.generateDifferentiableTypeAttr(attribute: node)?.asTypeAttribute)
|
||||
.map(BridgedTypeOrCustomAttr.typeAttr(_:))
|
||||
case .Lifetime:
|
||||
return (self.generateLifetimeTypeAttr(attribute: node)?.asTypeAttribute)
|
||||
.map(BridgedTypeOrCustomAttr.typeAttr(_:))
|
||||
case .OpaqueReturnTypeOf:
|
||||
return (self.generateOpaqueReturnTypeOfTypeAttr(attribute: node)?.asTypeAttribute)
|
||||
.map(BridgedTypeOrCustomAttr.typeAttr(_:))
|
||||
@@ -247,6 +250,20 @@ extension ASTGenVisitor {
|
||||
kindLoc: differentiabilityLoc
|
||||
)
|
||||
}
|
||||
|
||||
func generateLifetimeTypeAttr(attribute node: AttributeSyntax) -> BridgedLifetimeTypeAttr? {
|
||||
guard let entry = self.generateLifetimeEntry(attribute: node) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return .createParsed(
|
||||
self.ctx,
|
||||
atLoc: self.generateSourceLoc(node.atSign),
|
||||
nameLoc: self.generateSourceLoc(node.attributeName),
|
||||
parensRange: self.generateAttrParensRange(attribute: node),
|
||||
entry: entry
|
||||
)
|
||||
}
|
||||
|
||||
func generateIsolatedTypeAttr(attribute node: AttributeSyntax) -> BridgedIsolatedTypeAttr? {
|
||||
let isolationKindLoc = self.generateSourceLoc(node.arguments)
|
||||
|
||||
@@ -2104,7 +2104,8 @@ namespace {
|
||||
}
|
||||
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
|
||||
LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, resultIndex,
|
||||
/*isImmortal*/ true);
|
||||
/*isImmortal*/ true,
|
||||
/*isFromAnnotation*/ true);
|
||||
lifetimeDependencies.push_back(immortalLifetime);
|
||||
Impl.SwiftContext.evaluator.cacheOutput(
|
||||
LifetimeDependenceInfoRequest{fd},
|
||||
@@ -4230,7 +4231,7 @@ namespace {
|
||||
lifetimeDependencies.push_back(LifetimeDependenceInfo(
|
||||
nullptr, IndexSubset::get(Impl.SwiftContext, dependenciesOfRet),
|
||||
returnIdx,
|
||||
/*isImmortal*/ false));
|
||||
/*isImmortal*/ false, /*isFromAnnotation*/ true));
|
||||
Impl.SwiftContext.evaluator.cacheOutput(
|
||||
LifetimeDependenceInfoRequest{result},
|
||||
Impl.SwiftContext.AllocateCopy(lifetimeDependencies));
|
||||
@@ -4299,7 +4300,8 @@ namespace {
|
||||
|
||||
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
|
||||
LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, 0,
|
||||
/*isImmortal*/ true);
|
||||
/*isImmortal*/ true,
|
||||
/*isFromAnnotation*/ true);
|
||||
if (hasUnsafeAPIAttr(decl) && !isEscapable(decl->getReturnType())) {
|
||||
lifetimeDependencies.push_back(immortalLifetime);
|
||||
Impl.SwiftContext.evaluator.cacheOutput(
|
||||
@@ -4387,8 +4389,11 @@ namespace {
|
||||
cast<clang::CXXMethodDecl>(decl)->getThisType()->getPointeeType());
|
||||
|
||||
for (auto& [idx, inheritedDepVec]: inheritedArgDependences) {
|
||||
lifetimeDependencies.push_back(LifetimeDependenceInfo(inheritedDepVec.any() ? IndexSubset::get(Impl.SwiftContext,
|
||||
inheritedDepVec): nullptr, nullptr, idx, /*isImmortal=*/false));
|
||||
lifetimeDependencies.push_back(LifetimeDependenceInfo(
|
||||
inheritedDepVec.any()
|
||||
? IndexSubset::get(Impl.SwiftContext, inheritedDepVec)
|
||||
: nullptr,
|
||||
nullptr, idx, /*isImmortal=*/false, /*isFromAnnotation=*/true));
|
||||
}
|
||||
|
||||
if (inheritLifetimeParamIndicesForReturn.any() ||
|
||||
@@ -4403,7 +4408,7 @@ namespace {
|
||||
scopedLifetimeParamIndicesForReturn)
|
||||
: nullptr,
|
||||
returnIdx,
|
||||
/*isImmortal*/ false));
|
||||
/*isImmortal*/ false, /*isFromAnnotation*/ true));
|
||||
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) {
|
||||
// Assume default constructed view types have no dependencies.
|
||||
if (ctordecl->isDefaultConstructor() &&
|
||||
|
||||
@@ -5076,6 +5076,22 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result,
|
||||
return makeParserSuccess();
|
||||
}
|
||||
|
||||
case TypeAttrKind::Lifetime: {
|
||||
const auto entryResult = parseLifetimeEntry(AtLoc);
|
||||
if (entryResult.isNull()) {
|
||||
return makeParserError();
|
||||
}
|
||||
|
||||
auto *entry = entryResult.get();
|
||||
|
||||
if (!justChecking) {
|
||||
result = new (Context) LifetimeTypeAttr(
|
||||
AtLoc, attrLoc, SourceRange(entry->getStartLoc(), entry->getEndLoc()),
|
||||
entryResult.get());
|
||||
}
|
||||
return makeParserSuccess();
|
||||
}
|
||||
|
||||
case TypeAttrKind::Convention: {
|
||||
ConventionTypeAttr *convention = nullptr;
|
||||
if (parseConventionAttributeInternal(AtLoc, attrLoc, convention,
|
||||
|
||||
@@ -2957,8 +2957,10 @@ static CanSILFunctionType getSILFunctionType(
|
||||
= [&](const LifetimeDependenceInfo &formalDeps,
|
||||
unsigned target) -> LifetimeDependenceInfo {
|
||||
if (formalDeps.isImmortal()) {
|
||||
return LifetimeDependenceInfo(nullptr, nullptr,
|
||||
target, /*immortal*/ true);
|
||||
return LifetimeDependenceInfo(
|
||||
nullptr, nullptr, target,
|
||||
/*immortal*/ true,
|
||||
/*fromAnnotation*/ formalDeps.isFromAnnotation());
|
||||
}
|
||||
|
||||
auto lowerIndexSet = [&](IndexSubset *formal) -> IndexSubset * {
|
||||
@@ -2992,8 +2994,10 @@ static CanSILFunctionType getSILFunctionType(
|
||||
// entirely (such as if they were of `()` type), then there is effectively
|
||||
// no dependency, leaving behind an immortal value.
|
||||
if (!inheritIndicesSet && !scopeIndicesSet) {
|
||||
return LifetimeDependenceInfo(nullptr, nullptr, target,
|
||||
/*immortal*/ true);
|
||||
return LifetimeDependenceInfo(
|
||||
nullptr, nullptr, target,
|
||||
/*immortal*/ true,
|
||||
/*fromAnnotation*/ formalDeps.isFromAnnotation());
|
||||
}
|
||||
|
||||
SmallBitVector addressableDeps = scopeIndicesSet
|
||||
@@ -3009,12 +3013,11 @@ static CanSILFunctionType getSILFunctionType(
|
||||
IndexSubset *condAddressableSet = condAddressableDeps.any()
|
||||
? IndexSubset::get(TC.Context, condAddressableDeps)
|
||||
: nullptr;
|
||||
|
||||
return LifetimeDependenceInfo(inheritIndicesSet,
|
||||
scopeIndicesSet,
|
||||
target, /*immortal*/ false,
|
||||
addressableSet,
|
||||
condAddressableSet);
|
||||
|
||||
return LifetimeDependenceInfo(
|
||||
inheritIndicesSet, scopeIndicesSet, target, /*immortal*/ false,
|
||||
/*fromAnnotation*/ formalDeps.isFromAnnotation(), addressableSet,
|
||||
condAddressableSet);
|
||||
};
|
||||
// Lower parameter dependencies.
|
||||
for (unsigned i = 0; i < parameterMap.size(); ++i) {
|
||||
|
||||
+118
-43
@@ -2468,7 +2468,7 @@ namespace {
|
||||
CallerIsolatedTypeRepr *nonisolatedNonsendingAttr;
|
||||
|
||||
llvm::TinyPtrVector<CustomAttr*> customAttrs;
|
||||
EnumMap<TypeAttrKind, TypeAttribute *> typeAttrs;
|
||||
EnumMap<TypeAttrKind, llvm::TinyPtrVector<TypeAttribute *>> typeAttrs;
|
||||
|
||||
llvm::SmallBitVector claimedCustomAttrs;
|
||||
FixedBitSet<NumTypeAttrKinds> claimedTypeAttrs;
|
||||
@@ -2515,33 +2515,35 @@ namespace {
|
||||
claimedCustomAttrs.set(it - customAttrs.begin());
|
||||
}
|
||||
|
||||
TypeAttribute *getWithoutClaiming(TypeAttrKind attrKind) {
|
||||
ArrayRef<TypeAttribute *> getWithoutClaiming(TypeAttrKind attrKind) {
|
||||
auto it = typeAttrs.find(attrKind);
|
||||
if (it != typeAttrs.end()) {
|
||||
return *it;
|
||||
} else {
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/// Claim the attribute matching the given representative kind.
|
||||
/// It will not be diagnosed as unused.
|
||||
TypeAttribute *claim(TypeAttrKind attrKind) {
|
||||
ArrayRef<TypeAttribute *> claim(TypeAttrKind attrKind) {
|
||||
assert(getRepresentative(attrKind) == attrKind);
|
||||
auto it = typeAttrs.find(attrKind);
|
||||
if (it != typeAttrs.end()) {
|
||||
claimedTypeAttrs.insert(it - typeAttrs.begin());
|
||||
return *it;
|
||||
} else {
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/// Claim all attributes for which the given function returns true.
|
||||
void claimAllWhere(ContextualTypeAttrResolver resolver) {
|
||||
size_t i = 0;
|
||||
for (TypeAttribute *attr : typeAttrs) {
|
||||
if (resolver(attr))
|
||||
for (auto &attrVector : typeAttrs) {
|
||||
// Only claim the attribute if the resolver matches for every instance
|
||||
// of it.
|
||||
if (llvm::all_of(attrVector, resolver))
|
||||
claimedTypeAttrs.insert(i);
|
||||
i++;
|
||||
}
|
||||
@@ -2551,8 +2553,10 @@ namespace {
|
||||
/// but process them in reverse source order.
|
||||
void reversedClaimAllWhere(ContextualTypeAttrResolver resolver) {
|
||||
for (size_t i = typeAttrs.size(); i > 0; --i) {
|
||||
TypeAttribute *attr = typeAttrs.begin()[i - 1];
|
||||
if (resolver(attr))
|
||||
auto &attrVector = typeAttrs.begin()[i - 1];
|
||||
// Only claim the attribute if the resolver matches for every instance
|
||||
// of it.
|
||||
if (llvm::all_of(attrVector, resolver))
|
||||
claimedTypeAttrs.insert(i - 1);
|
||||
}
|
||||
}
|
||||
@@ -2584,33 +2588,66 @@ namespace {
|
||||
}
|
||||
};
|
||||
|
||||
template <class AttrClass>
|
||||
AttrClass *claim(TypeAttrSet &attrs) {
|
||||
auto attr = attrs.claim(AttrClass::StaticKind);
|
||||
return cast_or_null<AttrClass>(attr);
|
||||
template <TypeAttrKind Kind>
|
||||
auto claim(TypeAttrSet &attrs) {
|
||||
auto attrVector = attrs.claim(Kind);
|
||||
if constexpr (!TypeAttribute::allowMultipleAttributes(Kind)) {
|
||||
return attrVector.empty() ? nullptr : attrVector.front();
|
||||
} else {
|
||||
return attrVector;
|
||||
}
|
||||
}
|
||||
|
||||
template <class AttrClass>
|
||||
AttrClass *claim(TypeAttrSet *attrs) {
|
||||
return (attrs ? claim<AttrClass>(*attrs) : nullptr);
|
||||
auto claim(TypeAttrSet &attrs) {
|
||||
ArrayRef<TypeAttribute *> attrVector = attrs.claim(AttrClass::StaticKind);
|
||||
if constexpr (!TypeAttribute::allowMultipleAttributes(
|
||||
AttrClass::StaticKind)) {
|
||||
return attrVector.empty() ? nullptr
|
||||
: cast_or_null<AttrClass>(attrVector.front());
|
||||
} else {
|
||||
// The type attributes in attrVector are all instances of the type
|
||||
// attribute that corresponds to the claimed type attribute kind (i.e.
|
||||
// AttrClass) so casting the data pointer should always be safe. Since we
|
||||
// cannot statically prove this, perform a paranoid check.
|
||||
if (llvm::all_of(attrVector, [](TypeAttribute *attr) {
|
||||
return isa<AttrClass>(attr);
|
||||
})) {
|
||||
return ArrayRef<AttrClass *>(
|
||||
reinterpret_cast<AttrClass *const *>(attrVector.data()),
|
||||
attrVector.size());
|
||||
} else {
|
||||
return ArrayRef<AttrClass *>{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class AttrClass>
|
||||
AttrClass *getWithoutClaiming(TypeAttrSet &attrs) {
|
||||
auto attr = attrs.getWithoutClaiming(AttrClass::StaticKind);
|
||||
return cast_or_null<AttrClass>(attr);
|
||||
auto claim(TypeAttrSet *attrs) {
|
||||
return (attrs ? claim<AttrClass>(*attrs)
|
||||
: decltype(claim<AttrClass>(*attrs)){});
|
||||
}
|
||||
|
||||
template <class AttrClass>
|
||||
std::enable_if_t<std::is_base_of_v<TypeAttribute, AttrClass>, AttrClass *>
|
||||
getWithoutClaiming(TypeAttrSet *attrs) {
|
||||
return (attrs ? getWithoutClaiming<AttrClass>(*attrs) : nullptr);
|
||||
auto getWithoutClaiming(TypeAttrSet &attrs) {
|
||||
auto attrVector = attrs.getWithoutClaiming(AttrClass::StaticKind);
|
||||
if constexpr (!TypeAttribute::allowMultipleAttributes(
|
||||
AttrClass::StaticKind)) {
|
||||
return attrVector.empty() ? nullptr
|
||||
: cast_or_null<AttrClass>(attrVector.front());
|
||||
} else {
|
||||
return attrVector;
|
||||
}
|
||||
}
|
||||
|
||||
template <class AttrClass>
|
||||
std::enable_if_t<std::is_same_v<AttrClass, CallerIsolatedTypeRepr>,
|
||||
CallerIsolatedTypeRepr *>
|
||||
getWithoutClaiming(TypeAttrSet *attrs) {
|
||||
auto getWithoutClaiming(TypeAttrSet *attrs) {
|
||||
return (attrs ? getWithoutClaiming<AttrClass>(*attrs)
|
||||
: decltype(getWithoutClaiming<AttrClass>(*attrs)){});
|
||||
}
|
||||
|
||||
template <>
|
||||
auto getWithoutClaiming<CallerIsolatedTypeRepr>(TypeAttrSet *attrs) {
|
||||
return attrs ? attrs->getNonisolatedNonsendingAttr() : nullptr;
|
||||
}
|
||||
} // end anonymous namespace
|
||||
@@ -3282,8 +3319,15 @@ void TypeAttrSet::accumulate(ArrayRef<TypeOrCustomAttr> attrs) {
|
||||
auto insertResult = typeAttrs.insert(representativeKind, typeAttr);
|
||||
if (insertResult.second) continue;
|
||||
|
||||
// If an attribute with the same kind already exists, only add this one if
|
||||
// there can be more than one.
|
||||
if (TypeAttribute::allowMultipleAttributes(representativeKind)) {
|
||||
insertResult.first->push_back(typeAttr);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dignose the conflict.
|
||||
TypeAttribute *previousAttr = *insertResult.first;
|
||||
TypeAttribute *previousAttr = insertResult.first->front();
|
||||
|
||||
diagnoseConflict(representativeKind, previousAttr, typeAttr);
|
||||
}
|
||||
@@ -3340,11 +3384,12 @@ void TypeAttrSet::diagnoseUnclaimed(const TypeResolution &resolution,
|
||||
|
||||
// Type attributes
|
||||
size_t i = 0;
|
||||
for (auto attr : typeAttrs) {
|
||||
for (auto const &attrVector : typeAttrs) {
|
||||
if (claimedTypeAttrs.contains(i)) continue;
|
||||
i++;
|
||||
|
||||
diagnoseUnclaimed(attr, resolution, options, resolvedType);
|
||||
for (auto attr : attrVector)
|
||||
diagnoseUnclaimed(attr, resolution, options, resolvedType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3517,7 +3562,7 @@ TypeResolver::resolveAttributedType(TypeRepr *repr, TypeResolutionOptions option
|
||||
|
||||
// These are the total type transforms.
|
||||
Type ty;
|
||||
if (auto attr = attrs.claim(TAR_TypeTransformer)) {
|
||||
if (auto attr = claim<TAR_TypeTransformer>(attrs)) {
|
||||
if (auto opaqueAttr = dyn_cast<OpaqueReturnTypeOfTypeAttr>(attr)) {
|
||||
ty = resolveOpaqueReturnType(repr, opaqueAttr->getMangledName(),
|
||||
opaqueAttr->getIndex(),
|
||||
@@ -3531,8 +3576,8 @@ TypeResolver::resolveAttributedType(TypeRepr *repr, TypeResolutionOptions option
|
||||
|
||||
// The SIL metatype attributes are basically total type transforms, too.
|
||||
// TODO: this should really be restricted to lowered types
|
||||
} else if (auto attr = isSILSourceFile()
|
||||
? attrs.claim(TAR_SILMetatype) : nullptr) {
|
||||
} else if (auto attr =
|
||||
isSILSourceFile() ? claim<TAR_SILMetatype>(attrs) : nullptr) {
|
||||
ty = resolveSILMetatype(repr, options, attr);
|
||||
|
||||
// Okay, propagate attributes down to specific resolvers.
|
||||
@@ -4527,21 +4572,50 @@ NeverNullType TypeResolver::resolveASTFunctionType(
|
||||
.build();
|
||||
|
||||
// SIL uses polymorphic function types to resolve overloaded member functions.
|
||||
AnyFunctionType *aft;
|
||||
if (auto genericSig = repr->getGenericSignature()) {
|
||||
return GenericFunctionType::get(genericSig, params, outputTy, extInfo);
|
||||
aft = GenericFunctionType::get(genericSig, params, outputTy, extInfo);
|
||||
} else {
|
||||
|
||||
auto fnTy = FunctionType::get(params, outputTy, extInfo);
|
||||
if (fnTy->hasError())
|
||||
return fnTy;
|
||||
|
||||
if (TypeChecker::diagnoseInvalidFunctionType(fnTy, repr->getLoc(), repr,
|
||||
getDeclContext(),
|
||||
resolution.getStage()))
|
||||
return ErrorType::get(fnTy);
|
||||
|
||||
aft = fnTy;
|
||||
}
|
||||
|
||||
auto fnTy = FunctionType::get(params, outputTy, extInfo);
|
||||
|
||||
if (fnTy->hasError())
|
||||
return fnTy;
|
||||
auto const lifetimeAttributes = claim<LifetimeTypeAttr>(attrs);
|
||||
|
||||
if (TypeChecker::diagnoseInvalidFunctionType(fnTy, repr->getLoc(), repr,
|
||||
getDeclContext(),
|
||||
resolution.getStage()))
|
||||
return ErrorType::get(fnTy);
|
||||
// Lifetime dependence inference has to map parameter and result types into
|
||||
// the generic environment of the DeclContext, so it must request the generic
|
||||
// signature of the type. In order to prevent cycles in request evaluation, we
|
||||
// defer lifetime dependence checking until the Interface type resolution
|
||||
// stage. Since the analysis depends on the type's generic environment, it can
|
||||
// only run after a type has already been produced.
|
||||
if (!ctx.LangOpts.hasFeature(Feature::Lifetimes) &&
|
||||
!lifetimeAttributes.empty()) {
|
||||
diagnose(lifetimeAttributes[0]->getAttrLoc(),
|
||||
diag::requires_experimental_feature, "@_lifetime", false,
|
||||
Feature::Lifetimes.getName());
|
||||
return ErrorType::get(getASTContext());
|
||||
}
|
||||
|
||||
return fnTy;
|
||||
if (inStage(TypeResolutionStage::Interface)) {
|
||||
if (auto const resolvedLifetimeDependence =
|
||||
LifetimeDependenceInfo::getFromAST(
|
||||
repr, aft, lifetimeAttributes, getDeclContext(),
|
||||
resolution.getGenericSignature().getGenericEnvironment())) {
|
||||
aft = aft->withExtInfo(aft->getExtInfo().withLifetimeDependencies(
|
||||
*resolvedLifetimeDependence));
|
||||
}
|
||||
}
|
||||
|
||||
return aft;
|
||||
}
|
||||
|
||||
NeverNullType TypeResolver::resolveSILBoxType(SILBoxTypeRepr *repr,
|
||||
@@ -4617,7 +4691,7 @@ NeverNullType TypeResolver::resolveSILFunctionType(FunctionTypeRepr *repr,
|
||||
bool hasError = false;
|
||||
|
||||
auto coroutineKind = SILCoroutineKind::None;
|
||||
if (auto coroAttr = attrs ? attrs->claim(TAR_SILCoroutine) : nullptr) {
|
||||
if (auto coroAttr = attrs ? claim<TAR_SILCoroutine>(*attrs) : nullptr) {
|
||||
assert(isa<YieldOnceTypeAttr>(coroAttr) ||
|
||||
isa<YieldOnce2TypeAttr>(coroAttr) ||
|
||||
isa<YieldManyTypeAttr>(coroAttr));
|
||||
@@ -4637,7 +4711,8 @@ NeverNullType TypeResolver::resolveSILFunctionType(FunctionTypeRepr *repr,
|
||||
}
|
||||
|
||||
ParameterConvention callee = ParameterConvention::Direct_Unowned;
|
||||
if (auto calleeAttr = attrs ? attrs->claim(TAR_SILCalleeConvention) : nullptr) {
|
||||
if (auto calleeAttr =
|
||||
attrs ? claim<TAR_SILCalleeConvention>(*attrs) : nullptr) {
|
||||
assert(isa<CalleeOwnedTypeAttr>(calleeAttr) ||
|
||||
isa<CalleeGuaranteedTypeAttr>(calleeAttr));
|
||||
callee = (isa<CalleeOwnedTypeAttr>(calleeAttr)
|
||||
@@ -5054,7 +5129,7 @@ bool TypeResolver::resolveSingleSILResult(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto conventionAttr = attrs.claim(TAR_SILValueConvention)) {
|
||||
if (auto conventionAttr = claim<TAR_SILValueConvention>(attrs)) {
|
||||
switch (conventionAttr->getKind()) {
|
||||
#define ERROR(ATTR, CONVENTION) \
|
||||
case TypeAttrKind::ATTR: \
|
||||
|
||||
@@ -9588,12 +9588,13 @@ ModuleFile::maybeReadLifetimeDependence() {
|
||||
unsigned targetIndex;
|
||||
unsigned paramIndicesLength;
|
||||
bool isImmortal;
|
||||
bool isFromAnnotation;
|
||||
bool hasInheritLifetimeParamIndices;
|
||||
bool hasScopeLifetimeParamIndices;
|
||||
bool hasAddressableParamIndices;
|
||||
ArrayRef<uint64_t> lifetimeDependenceData;
|
||||
LifetimeDependenceLayout::readRecord(
|
||||
scratch, targetIndex, paramIndicesLength, isImmortal,
|
||||
scratch, targetIndex, paramIndicesLength, isImmortal, isFromAnnotation,
|
||||
hasInheritLifetimeParamIndices, hasScopeLifetimeParamIndices,
|
||||
hasAddressableParamIndices, lifetimeDependenceData);
|
||||
|
||||
@@ -9629,8 +9630,9 @@ ModuleFile::maybeReadLifetimeDependence() {
|
||||
hasScopeLifetimeParamIndices
|
||||
? IndexSubset::get(ctx, scopeLifetimeParamIndices)
|
||||
: nullptr,
|
||||
targetIndex, isImmortal,
|
||||
targetIndex, isImmortal, isFromAnnotation,
|
||||
hasAddressableParamIndices
|
||||
? IndexSubset::get(ctx, addressableParamIndices)
|
||||
: nullptr);
|
||||
: nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
|
||||
/// describe what change you made. The content of this comment isn't important;
|
||||
/// it just ensures a conflict if two people change the module format.
|
||||
/// Don't worry about adhering to the 80-column limit for this line.
|
||||
const uint16_t SWIFTMODULE_VERSION_MINOR = 983; // @reparented inherited entry in support for Reparenting feature
|
||||
const uint16_t SWIFTMODULE_VERSION_MINOR = 984; // @_lifetime attribute isFromAnnotation flag
|
||||
|
||||
/// A standard hash seed used for all string hashes in a serialized module.
|
||||
///
|
||||
@@ -2366,6 +2366,7 @@ namespace decls_block {
|
||||
BCVBR<4>, // targetIndex
|
||||
BCVBR<4>, // paramIndicesLength
|
||||
BCFixed<1>, // isImmortal
|
||||
BCFixed<1>, // isFromAnnotation
|
||||
BCFixed<1>, // hasInheritLifetimeParamIndices
|
||||
BCFixed<1>, // hasScopeLifetimeParamIndices
|
||||
BCFixed<1>, // hasAddressableParamIndices
|
||||
|
||||
@@ -2780,7 +2780,7 @@ void Serializer::writeLifetimeDependencies(
|
||||
LifetimeDependenceLayout::emitRecord(
|
||||
Out, ScratchRecord, abbrCode, info.getTargetIndex(),
|
||||
info.getParamIndicesLength(), info.isImmortal(),
|
||||
info.hasInheritLifetimeParamIndices(),
|
||||
info.isFromAnnotation(), info.hasInheritLifetimeParamIndices(),
|
||||
info.hasScopeLifetimeParamIndices(), info.hasAddressableParamIndices(),
|
||||
paramIndices);
|
||||
paramIndices.clear();
|
||||
|
||||
@@ -89,3 +89,30 @@ public struct RigidArray : ~Copyable {
|
||||
}
|
||||
}
|
||||
|
||||
// Function types
|
||||
|
||||
@inlinable
|
||||
@_lifetime(copy ne0)
|
||||
public func takeCopier(f: @_lifetime(io: copy io) @_lifetime(copy inview) (_ inview: consuming AnotherView, _ io: inout AnotherView) -> AnotherView, ne0: consuming AnotherView, ne1: inout AnotherView) -> AnotherView {
|
||||
let ne2 = f(ne0, &ne1)
|
||||
return ne2
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func takeCopierUnannotated(f: (consuming AnotherView) -> AnotherView) {}
|
||||
|
||||
public typealias ExplicitNestedType = @_lifetime(copy ne0) @_lifetime(ne1: copy ne0) ((AnotherView) -> AnotherView, _ ne0: consuming AnotherView, _ ne1: inout AnotherView) -> AnotherView
|
||||
|
||||
@inlinable
|
||||
public func takeExplicitNestedType(f: ExplicitNestedType) {}
|
||||
|
||||
|
||||
@inlinable
|
||||
public func returnableCopier(_ aView: AnotherView) -> AnotherView {
|
||||
return aView
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func returnCopier() -> @_lifetime(copy a) (_ a: AnotherView) -> AnotherView {
|
||||
return returnableCopier
|
||||
}
|
||||
|
||||
@@ -123,3 +123,45 @@ import lifetime_underscored_dependence
|
||||
// CHECK: }
|
||||
// CHECK: #endif
|
||||
// CHECK: }
|
||||
|
||||
// CHECK: #if compiler(>=5.3) && $ClosureLifetimes
|
||||
// CHECK-NEXT: #if compiler(>=5.3) && $Lifetimes
|
||||
// CHECK-NEXT: @_lifetime(copy ne0)
|
||||
// CHECK-NEXT: @inlinable public func takeCopier(f: @_lifetime(io: copy io) @_lifetime(copy inview) (_ inview: consuming lifetime_underscored_dependence.AnotherView, _ io: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, ne0: consuming lifetime_underscored_dependence.AnotherView, ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView {
|
||||
// CHECK-NEXT: let ne2 = f(ne0, &ne1)
|
||||
// CHECK-NEXT: return ne2
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: #else
|
||||
// CHECK-NEXT: @lifetime(copy ne0)
|
||||
// CHECK-NEXT: @inlinable public func takeCopier(f: @_lifetime(io: copy io) @_lifetime(copy inview) (_ inview: consuming lifetime_underscored_dependence.AnotherView, _ io: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, ne0: consuming lifetime_underscored_dependence.AnotherView, ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView {
|
||||
// CHECK-NEXT: let ne2 = f(ne0, &ne1)
|
||||
// CHECK-NEXT: return ne2
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: #endif
|
||||
// CHECK-NEXT: #endif
|
||||
|
||||
// CHECK: @inlinable public func takeCopierUnannotated(f: (consuming lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView) {}
|
||||
|
||||
// CHECK: #if compiler(>=5.3) && $ClosureLifetimes
|
||||
// CHECK-NEXT: public typealias ExplicitNestedType = @_lifetime(copy ne0) @_lifetime(ne1: copy ne0) ((lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, _ ne0: consuming lifetime_underscored_dependence.AnotherView, _ ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView
|
||||
// CHECK-NEXT: #endif
|
||||
|
||||
// CHECK: #if compiler(>=5.3) && $ClosureLifetimes
|
||||
// CHECK-NEXT: @inlinable public func takeExplicitNestedType(f: @_lifetime(copy ne0) @_lifetime(ne1: copy ne0) ((lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, _ ne0: consuming lifetime_underscored_dependence.AnotherView, _ ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView) {}
|
||||
// CHECK-NEXT: #endif
|
||||
|
||||
// CHECK: #if compiler(>=5.3) && $Lifetimes
|
||||
// CHECK-NEXT: @inlinable public func returnableCopier(_ aView: lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView {
|
||||
// CHECK-NEXT: return aView
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: #else
|
||||
// CHECK-NEXT: @inlinable public func returnableCopier(_ aView: lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView {
|
||||
// CHECK-NEXT: return aView
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: #endif
|
||||
|
||||
// CHECK: #if compiler(>=5.3) && $ClosureLifetimes
|
||||
// CHECK-NEXT: @inlinable public func returnCopier() -> @_lifetime(copy a) (_ a: lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView {
|
||||
// CHECK-NEXT: return returnableCopier
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: #endif
|
||||
|
||||
@@ -8,6 +8,46 @@
|
||||
|
||||
import Builtin
|
||||
|
||||
struct NE: ~Escapable {}
|
||||
|
||||
// Function type lifetime dependencies are printed as they appeared in Swift.
|
||||
// Internal labels are preserved when a function type has explicit lifetime
|
||||
// dependence information, since these may be used to refer to the sources and
|
||||
// targets of lifetimes.
|
||||
|
||||
// CHECK-LABEL: typealias LabelledNE2NE = @_lifetime(copy ne) (_ ne: NE) -> NE
|
||||
typealias LabelledNE2NE = @_lifetime(copy ne) (_ ne: NE) -> NE
|
||||
// CHECK-LABEL: typealias InferredNE2NE = (NE) -> NE
|
||||
typealias InferredNE2NE = (NE) -> NE
|
||||
// CHECK-LABEL: typealias InferredLabelledNE2NE = (NE) -> NE
|
||||
typealias InferredLabelledNE2NE = (_ ne: NE) -> NE
|
||||
|
||||
// CHECK-LABEL: typealias NamedLifetimeType = @_lifetime(copy ne) (_ ne: NE, _ ne2: NE) -> NE
|
||||
typealias NamedLifetimeType = @_lifetime(copy ne) (_ ne: NE, _ ne2: NE) -> NE
|
||||
// CHECK-LABEL: typealias InferredLifetimeType = (NE, NE) -> NE
|
||||
typealias InferredLifetimeType = (_ ne: NE, NE) -> NE
|
||||
// CHECK-LABEL: typealias ImmortalLifetimeType = @_lifetime(immortal) (_ ne: NE, _ ne2: NE) -> NE
|
||||
typealias ImmortalLifetimeType = @_lifetime(immortal) (_ ne: NE, _ ne2: NE) -> NE
|
||||
|
||||
// CHECK-LABEL: typealias NoLifetimeType = (Int) -> Int
|
||||
typealias NoLifetimeType = (_ x: Int) -> Int
|
||||
|
||||
// CHECK: typealias MixedLifetimeType = @_lifetime(copy ne0) (_ ne0: NE, _ neio: inout NE) -> NE
|
||||
typealias MixedLifetimeType = @_lifetime(copy ne0) (_ ne0: NE, _ neio: inout NE) -> NE
|
||||
// CHECK: typealias NestedLifetimeType = @_lifetime(neo: copy ne0) (_ nei: inout NE, _ ne0: NE, _ neo: inout NE) -> (NE) -> NE
|
||||
typealias NestedLifetimeType = @_lifetime(neo: copy ne0) (_ nei: inout NE, _ ne0: NE, _ neo: inout NE) -> (_ ne1: NE) -> NE
|
||||
|
||||
// CHECK-LABEL: typealias NestedType = @_lifetime(copy ne2) @_lifetime(ne3: copy ne2) (@_lifetime(copy ne0) @_lifetime(ne1: copy ne1) (_ ne0: NE, _ ne1: inout NE) -> NE, _ ne2: consuming NE, _ ne3: inout NE) -> NE
|
||||
typealias NestedType =
|
||||
@_lifetime(copy ne2) @_lifetime(ne3: copy ne2)
|
||||
(@_lifetime(copy ne0) @_lifetime(ne1: copy ne1) (_ ne0: NE, _ ne1: inout NE) -> NE,
|
||||
_ ne2: consuming NE, _ ne3: inout NE) -> NE
|
||||
|
||||
// CHECK-LABEL: func takeNestedType(f: @_lifetime(copy ne2) @_lifetime(ne3: copy ne2) (@_lifetime(copy ne0) @_lifetime(ne1: copy ne1) (_ ne0: NE, _ ne1: inout NE) -> NE, _ ne2: consuming NE, _ ne3: inout NE) -> NE)
|
||||
|
||||
// CHECK-LABEL: sil hidden @$s39explicit_lifetime_dependence_specifiers14takeNestedType1fyAA2NEVA2E_AEztXE_AEnAEztXE_tF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed (@guaranteed @noescape @callee_guaranteed (@guaranteed NE, @lifetime(copy 1) @inout NE) -> @lifetime(copy 0) @owned NE, @owned NE, @lifetime(copy 1) @inout NE) -> @lifetime(copy 1) @owned NE) -> () {
|
||||
func takeNestedType(f: NestedType) {}
|
||||
|
||||
struct BufferView : ~Escapable {
|
||||
let ptr: UnsafeRawBufferPointer
|
||||
// CHECK-LABEL: sil hidden @$s39explicit_lifetime_dependence_specifiers10BufferViewVyACSWcfC : $@convention(method) (UnsafeRawBufferPointer, @thin BufferView.Type) -> @lifetime(borrow 0) @owned BufferView {
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
|
||||
// REQUIRES: swift_feature_Lifetimes
|
||||
|
||||
struct NE: ~Escapable {}
|
||||
|
||||
// CHECK-LABEL: typealias ImplicitNestedType = ((NE, inout NE) -> NE, consuming NE, inout NE) -> NE
|
||||
typealias ImplicitNestedType = ((NE, inout NE) -> NE, consuming NE, inout NE) -> NE
|
||||
|
||||
// CHECK-LABEL: func takeImplicitNestedType(f: ((NE, inout NE) -> NE, consuming NE, inout NE) -> NE)
|
||||
|
||||
// CHECK-LABEL: sil hidden @$s28implicit_lifetime_dependence22takeImplicitNestedType1fyAA2NEVA2E_AEztXE_AEnAEztXE_tF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed (@guaranteed @noescape @callee_guaranteed (@guaranteed NE, @lifetime(copy 1) @inout NE) -> @lifetime(copy 0) @owned NE, @owned NE, @lifetime(copy 2) @inout NE) -> @lifetime(copy 1) @owned NE) -> () {
|
||||
func takeImplicitNestedType(f: ImplicitNestedType) {}
|
||||
|
||||
struct BufferView : ~Escapable {
|
||||
let ptr: UnsafeRawBufferPointer
|
||||
let c: Int
|
||||
@@ -228,3 +238,9 @@ public struct OuterNE: ~Escapable {
|
||||
self.inner1 = value
|
||||
}
|
||||
}
|
||||
|
||||
// rdar://160894371 (Infer @_lifetime(param: copy param) for inout closure arguments)
|
||||
// CHECK-LABEL: sil hidden @$s28implicit_lifetime_dependence20inoutClosureArgument1fyyAA2NEVzXE_tF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed (@lifetime(copy 0) @inout NE) -> ()) -> () {
|
||||
// CHECK-LABEL: } // end sil function '$s28implicit_lifetime_dependence20inoutClosureArgument1fyyAA2NEVzXE_tF'
|
||||
func inoutClosureArgument(f: (inout NE) -> ()) {
|
||||
}
|
||||
|
||||
@@ -103,3 +103,38 @@ func bvcons_capture_escapelet(bv: consuming BV) -> ()->Int { // expected-error *
|
||||
let closure = { bv.c }
|
||||
return closure
|
||||
}
|
||||
|
||||
// Higher-Order Function Tests
|
||||
|
||||
@_lifetime(other: copy bv)
|
||||
func take_assign_inout_copy(bv: BV, other: inout BV, f: @_lifetime(other: copy bv) (_ bv: BV, _ other: inout BV) -> ()) {
|
||||
f(bv, &other) // OK
|
||||
}
|
||||
|
||||
@_lifetime(other: borrow bv)
|
||||
func take_assign_inout_borrow(bv: BV, other: inout BV, f: @_lifetime(other: borrow bv) (_ bv: BV, _ other: inout BV) -> ()) {
|
||||
f(bv, &other) // OK
|
||||
}
|
||||
|
||||
@_lifetime(bv: copy bv)
|
||||
@_lifetime(other: copy bv)
|
||||
func take_bvmut_assign_inout(bv: inout BV, other: inout BV,
|
||||
f: @_lifetime(bv: copy bv) @_lifetime(other: copy bv)
|
||||
(_ bv: inout BV, _ other: inout BV) -> ()) {
|
||||
f(&bv, &other) // OK
|
||||
}
|
||||
|
||||
struct NE2: ~Escapable {}
|
||||
|
||||
@_lifetime(oNE: copy ne)
|
||||
func succeed_transfer_ne(ne: consuming NE2, oNE: inout NE2, f: (NE2) -> NE2) {
|
||||
oNE = f(ne) // OK
|
||||
}
|
||||
|
||||
func fail_smuggle_ne(oNE: inout NE2, f: (NE2) -> NE2) {
|
||||
// expected-error @-1 {{lifetime-dependent variable 'oNE' escapes its scope}}
|
||||
let ne = NE2()
|
||||
// expected-note @-1 {{it depends on the lifetime of this parent value}}
|
||||
oNE = f(ne)
|
||||
// expected-note @+1 {{this use causes the lifetime-dependent value to escape}}
|
||||
}
|
||||
|
||||
@@ -237,13 +237,6 @@ func testIndirectNonForwardedResult<T>(arg1: GNE<T>, arg2: GNE<T>) -> GNE<T> {
|
||||
forward(arg2) // expected-note {{this use causes the lifetime-dependent value to escape}}
|
||||
}
|
||||
|
||||
func testIndirectClosureResult<T>(f: () -> GNE<T>) -> GNE<T> {
|
||||
f()
|
||||
// expected-error @-1{{lifetime-dependent variable '$return_value' escapes its scope}}
|
||||
// expected-note @-3{{it depends on the lifetime of argument '$return_value'}}
|
||||
// expected-note @-3{{this use causes the lifetime-dependent value to escape}}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Coroutines
|
||||
// =============================================================================
|
||||
|
||||
@@ -13,3 +13,8 @@ func f_inout_infer(a: inout MutableRawSpan) {} // DEFAULT OK
|
||||
|
||||
func f_inout_no_infer(a: inout MutableRawSpan, b: RawSpan) {} // DEFAULT OK
|
||||
|
||||
typealias DeriveType = @_lifetime(copy ne) (_ ne: NE) -> NE // expected-error{{'@_lifetime' attribute is only valid when experimental feature Lifetimes is enabled}}
|
||||
|
||||
typealias InoutInferType = (inout MutableRawSpan) -> Void // DEFAULT OK
|
||||
|
||||
typealias InoutNoInferType = (inout MutableRawSpan, RawSpan) -> Void // DEFAULT OK
|
||||
|
||||
@@ -24,12 +24,17 @@ func applyAnnotatedTransfer(ne: NE, @_lifetime(0) transfer: (NE) -> NE) -> NE {
|
||||
}
|
||||
|
||||
@_lifetime(copy ne)
|
||||
func applyTransfer(ne: NE, transfer: (NE) -> NE) -> NE {
|
||||
func applyCorrectlyAnnotatedTransfer(ne: NE, transfer: @_lifetime(copy ne) (_ ne: NE) -> NE) -> NE { // OK
|
||||
transfer(ne)
|
||||
}
|
||||
|
||||
@_lifetime(copy ne)
|
||||
func applyTransfer(ne: NE, transfer: (NE) -> NE) -> NE { // OK, copy 0 inferred
|
||||
transfer(ne)
|
||||
}
|
||||
|
||||
func testTransfer(nc: consuming NC) {
|
||||
let transferred = applyTransfer(ne: nc.ne, transfer: transfer) // expected-error{{does not conform to expected type 'Escapable'}} e/xpected-error{{cannot convert value of type '(NE) -> @_lifetime(copy 0) NE' to expected argument type '(NE) -> NE'}}
|
||||
let transferred = applyTransfer(ne: nc.ne, transfer: transfer) // OK
|
||||
|
||||
_ = consume nc
|
||||
_ = transfer(transferred)
|
||||
@@ -45,8 +50,185 @@ func applyBorrow(nc: borrowing NC, borrow: (borrowing NC) -> NE) -> NE {
|
||||
}
|
||||
|
||||
func testBorrow(nc: consuming NC) {
|
||||
let borrowed = applyBorrow(nc: nc, borrow: borrow) // expected-error{{does not conform to expected type 'Escapable'}} ex/pected-error{{cannot convert value of type '(borrowing NC) -> @_lifetime(borrow 0) NE' to expected argument type '(borrowing NC) -> NE}}
|
||||
let borrowed = applyBorrow(nc: nc, borrow: borrow) // OK
|
||||
_ = consume nc
|
||||
_ = transfer(borrowed)
|
||||
}
|
||||
|
||||
// Tests adapted from lifetime_attr.swift for function types.
|
||||
class Klass {}
|
||||
typealias InvalidAttrOnNonExistingParamType = @_lifetime(copy nonexisting) (_ ne: NE) -> NE // expected-error{{invalid parameter name specified 'nonexisting'}}
|
||||
|
||||
typealias InvalidAttrOnNonExistingSelfType = @_lifetime(copy self) (_ ne: NE) -> NE // expected-error{{invalid lifetime dependence specifier on non-existent self}}
|
||||
|
||||
typealias InvalidAttrOnNonExistingParamIndexType = @_lifetime(2) (_ ne: NE) -> NE // expected-error{{invalid parameter index specified '2'}}
|
||||
|
||||
typealias InvalidDuplicateLifetimeDependenceType = @_lifetime(copy ne, borrow ne) (_ ne: borrowing NE) -> NE // expected-error{{duplicate lifetime dependence specifier}}
|
||||
|
||||
typealias InvalidDependenceConsumeKlassType = @_lifetime(borrow x) (_ x: consuming Klass) -> NE // expected-error{{invalid use of borrow dependence with consuming ownership}}
|
||||
|
||||
typealias InvalidDependenceBorrowKlassType = @_lifetime(&x) (_ x: borrowing Klass) -> NE // expected-error{{invalid use of & dependence with borrowing ownership}}
|
||||
// expected-note @-1{{use '@_lifetime(borrow x)' instead}}
|
||||
|
||||
typealias InvalidDependenceInoutKlassType = @_lifetime(borrow x) (_ x: inout Klass) -> NE // expected-error{{invalid use of borrow dependence with inout ownership}}
|
||||
// expected-note @-1{{use '@_lifetime(&x)' instead}}
|
||||
|
||||
typealias InvalidDependenceConsumeIntType = @_lifetime(borrow x) (_ x: consuming Int) -> NE // OK
|
||||
|
||||
typealias InvalidDependenceBorrowIntType = @_lifetime(&x) (_ x: borrowing Int) -> NE // expected-error{{invalid use of & dependence with borrowing ownership}}
|
||||
// expected-note @-1{{use '@_lifetime(borrow x)' instead}}
|
||||
|
||||
typealias InvalidDependenceInoutIntType = @_lifetime(borrow x) (_ x: inout Int) -> NE // expected-error{{invalid use of borrow dependence with inout ownership}}
|
||||
// expected-note @-1{{use '@_lifetime(&x)' instead}}
|
||||
|
||||
typealias InvalidTargetType =
|
||||
@_lifetime(result: copy source1)
|
||||
@_lifetime(result: copy source2) // expected-error{{invalid duplicate target lifetime dependencies on function}}
|
||||
(_ result: inout NE, _ source1: consuming NE, _ source2: consuming NE) -> ()
|
||||
|
||||
typealias InvalidSourceType =
|
||||
@_lifetime(result: copy source)
|
||||
@_lifetime(result: borrow source) // expected-error{{invalid duplicate target lifetime dependencies on function}}
|
||||
(_ result: inout NE, _ source: consuming NE) -> ()
|
||||
|
||||
typealias ImmortalConflictType = @_lifetime(immortal) (_ immortal: Int) -> NE // expected-error{{conflict between the parameter name and 'immortal' contextual keyword}}
|
||||
|
||||
typealias TestParameterDepType = @_lifetime(span: borrow holder) (_ holder: AnyObject, _ span: Span<Int>) -> () // expected-error{{lifetime-dependent parameter 'span' must be 'inout'}}
|
||||
|
||||
typealias InoutLifetimeDependenceType = @_lifetime(&ne) (_ ne: inout NE) -> NE
|
||||
|
||||
typealias DependOnEscapableType1 = @_lifetime(copy k) (_ k: inout Klass) -> NE // expected-error{{cannot copy the lifetime of an Escapable type}}
|
||||
// expected-note@-1{{use '@_lifetime(&k)' instead}}
|
||||
|
||||
typealias DependOnEscapableType2 = @_lifetime(copy k) (_ k: borrowing Klass) -> NE // expected-error{{cannot copy the lifetime of an Escapable type}}
|
||||
// expected-note@-1{{use '@_lifetime(borrow k)' instead}}
|
||||
|
||||
typealias DependOnEscapableType3 = @_lifetime(copy k) (_ k: consuming Klass) -> NE // expected-error{{cannot copy the lifetime of an Escapable type}}
|
||||
// expected-note@-1{{use '@_lifetime(borrow k)' instead}}
|
||||
|
||||
typealias GetIntType1 = @_lifetime(inValue) (_ inValue: Int) -> Int // expected-error{{invalid lifetime dependence on an Escapable result}}
|
||||
|
||||
typealias GetIntType2 = @_lifetime(outValue: borrow inValue) (_ outValue: inout Int, _ inValue: Int) -> () // expected-error{{invalid lifetime dependence on an Escapable target}}
|
||||
|
||||
typealias GetGenericEscapableType<T> = @_lifetime(inValue) (_ inValue: T) -> T // expected-error{{invalid lifetime dependence on an Escapable result}}
|
||||
|
||||
typealias GetGenericEscapableType2<T> =
|
||||
@_lifetime(outValue: borrow inValue) // expected-error{{invalid lifetime dependence on an Escapable target}}
|
||||
(_ outValue: inout T, _ inValue: T) -> ()
|
||||
|
||||
typealias GetGenericNonEscapableType2<T: ~Escapable> =
|
||||
@_lifetime(borrow inValue) (_ inValue: borrowing T) -> T // OK
|
||||
|
||||
typealias GetGenericCorrectType<T: ~Escapable> =
|
||||
@_lifetime(outValue: borrow inValue) (_ outValue: inout T, _ inValue: borrowing T) -> () // OK
|
||||
|
||||
@_lifetime(outValue: copy inValue) // OK
|
||||
func getGeneric<T : ~Escapable>(_ outValue: inout T, _ inValue: borrowing T) { // expected-note{{in call to function 'getGeneric'}}
|
||||
outValue = inValue
|
||||
}
|
||||
|
||||
@_lifetime(outValue: borrow inValue) // OK
|
||||
func getGeneric2<T : ~Escapable>(_ outValue: inout T, _ /* borrowing inferred */ inValue: T) {
|
||||
outValue = inValue
|
||||
}
|
||||
|
||||
@_lifetime(outValueI: immortal) // OK
|
||||
func getImmortalNE(_ outValueI: inout NE, _ inValueI: borrowing NE) {
|
||||
outValueI = NE()
|
||||
}
|
||||
|
||||
@_lifetime(o: borrow i) // OK
|
||||
func takeGetGenericAndArgs<T: ~Escapable>(f: @_lifetime(outValue: borrow inValue)
|
||||
(_ outValue: inout T, _ inValue: borrowing T) -> (), o: inout T, i: T) {
|
||||
f(&o, i)
|
||||
}
|
||||
|
||||
do {
|
||||
let x = NE()
|
||||
var y = NE()
|
||||
takeGetGenericAndArgs(f: getGeneric, o: &y, i: x)
|
||||
// expected-error@-1{{cannot convert value of type '(inout T, borrowing T) -> ()' to expected argument type '@_lifetime(outValue: borrow inValue) (_ outValue: inout NE, _ inValue: borrowing NE) -> ()'}}
|
||||
// expected-error@-2{{generic parameter 'T' could not be inferred}}
|
||||
}
|
||||
do {
|
||||
let x = NE()
|
||||
var y = NE()
|
||||
takeGetGenericAndArgs(f: getGeneric2, o: &y, i: x) // OK
|
||||
}
|
||||
do {
|
||||
let x = NE()
|
||||
var y = NE()
|
||||
takeGetGenericAndArgs(f: getImmortalNE, o: &y, i: x)
|
||||
// expected-error@-1{{cannot convert value of type '@_lifetime(0: immortal) (inout NE, borrowing NE) -> ()' to expected argument type '@_lifetime(outValue: borrow inValue) (_ outValue: inout NE, _ inValue: borrowing NE) -> ()'}}
|
||||
}
|
||||
do {
|
||||
let _ = transfer // OK
|
||||
let _: (NE) -> NE = transfer // OK
|
||||
let _: @_lifetime(copy ne) (_ ne: NE) -> NE = transfer // OK
|
||||
}
|
||||
|
||||
// rdar://166912068 (Incorrect error when passing a local function with a non-escapable parameter)
|
||||
struct NEWithSpan: ~Escapable {
|
||||
var span: RawSpan
|
||||
|
||||
@_lifetime(copy span)
|
||||
init(span: RawSpan) {
|
||||
self.span = span
|
||||
}
|
||||
}
|
||||
func takeBody(body: (inout NEWithSpan) -> Void) {}
|
||||
func checkNestedFunctions() {
|
||||
func doIt(ne: inout NEWithSpan) {}
|
||||
takeBody(body: doIt)
|
||||
}
|
||||
|
||||
// Bail-out cases where lifetime dependence checking cannot run.
|
||||
struct CNE<T: ~Escapable>: ~Escapable {
|
||||
let ne: T
|
||||
@_lifetime(copy ne)
|
||||
init(ne: T) {
|
||||
self.ne = ne
|
||||
}
|
||||
}
|
||||
|
||||
@_lifetime(borrow cne)
|
||||
func copyCNE(cne: CNE<NE>) -> CNE<NE> {
|
||||
return cne
|
||||
}
|
||||
|
||||
public let UnboundGenericParamFunctionType : (CNE) -> CNE<NE> = copyCNE // expected-error{{lifetime dependence checking failed due to unknown parameter type}}
|
||||
// expected-error@-1{{value of type 'CNE<NE>' does not conform to specified type 'Escapable'}}
|
||||
public let UnboundGenericParamFunctionTypeAnnotated : @_lifetime(borrow cne) (_ cne: CNE) -> CNE<NE> = copyCNE // expected-error{{lifetime dependence checking failed due to unknown parameter type}}
|
||||
// expected-error@-1{{value of type 'CNE<NE>' does not conform to specified type 'Escapable'}}
|
||||
public let UnboundGenericResultFunctionType : (CNE<NE>) -> CNE = copyCNE // expected-error{{lifetime dependence checking failed due to unknown result type}}
|
||||
// expected-error@-1{{value of type 'CNE<NE>' does not conform to specified type 'Escapable'}}
|
||||
public let UnboundGenericResultFunctionTypeAnnotated : @_lifetime(borrow cne) (_ cne: CNE<NE>) -> CNE = copyCNE // expected-error{{lifetime dependence checking failed due to unknown result type}}
|
||||
// expected-error@-1{{value of type 'CNE<NE>' does not conform to specified type 'Escapable'}}
|
||||
|
||||
public let TypeParameterParamFunctionType = copyCNE as (_) -> CNE<NE> // expected-error{{lifetime dependence checking failed due to unknown parameter type}}
|
||||
// expected-error@-1{{failed to produce diagnostic for expression}}
|
||||
public let TypeParameterParamFunctionTypeAnnotated = copyCNE as @_lifetime(borrow cne) (_ cne: _) -> CNE<NE> // expected-error{{lifetime dependence checking failed due to unknown parameter type}}
|
||||
// expected-error@-1{{failed to produce diagnostic for expression}}
|
||||
public let TypeParameterResultFunctionType = copyCNE as (CNE<NE>) -> _ // expected-error{{lifetime dependence checking failed due to unknown result type}}
|
||||
// expected-error@-1{{failed to produce diagnostic for expression}}
|
||||
public let TypeParameterResultFunctionTypeAnnotated = copyCNE as @_lifetime(borrow cne) (_ cne: CNE<NE>) -> _ // expected-error{{lifetime dependence checking failed due to unknown result type}}
|
||||
// expected-error@-1{{failed to produce diagnostic for expression}}
|
||||
|
||||
// Closure Context Dependence Tests
|
||||
|
||||
// This case should pass after we add support for dependencies on the closure context.
|
||||
//
|
||||
// We had to move it out of
|
||||
// SILOptimizer/lifetime_dependence/verify_diagnostics.swift because it causes
|
||||
// an error during type checking with the current version of function type
|
||||
// lifetime checking, preventing the SIL diagnostic checks that file tests from
|
||||
// running.
|
||||
//
|
||||
// TODO: Add more diagnostic tests when implementing closure context
|
||||
// dependencies, including SILOptimizer diagnostic tests such as the one this
|
||||
// originated as.
|
||||
func testIndirectClosureResult<T>(f: () -> CNE<T>) -> CNE<T> {
|
||||
// expected-error @-1{{a function with a ~Escapable result needs a parameter to depend on}}
|
||||
// expected-note @-2{{'@_lifetime(immortal)' can be used to indicate that values produced by this initializer have no lifetime dependencies}}
|
||||
f()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user