mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Merge pull request #60803 from hamishknight/part-and-partial
This commit is contained in:
@@ -107,6 +107,33 @@ enum class FreeTypeVariableBinding {
|
|||||||
UnresolvedType
|
UnresolvedType
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Describes whether or not a result builder method is supported.
|
||||||
|
struct ResultBuilderOpSupport {
|
||||||
|
enum Classification {
|
||||||
|
Unsupported,
|
||||||
|
Unavailable,
|
||||||
|
Supported
|
||||||
|
};
|
||||||
|
Classification Kind;
|
||||||
|
|
||||||
|
ResultBuilderOpSupport(Classification Kind) : Kind(Kind) {}
|
||||||
|
|
||||||
|
/// Returns whether or not the builder method is supported. If
|
||||||
|
/// \p requireAvailable is true, an unavailable method will be considered
|
||||||
|
/// unsupported.
|
||||||
|
bool isSupported(bool requireAvailable) const {
|
||||||
|
switch (Kind) {
|
||||||
|
case Unsupported:
|
||||||
|
return false;
|
||||||
|
case Unavailable:
|
||||||
|
return !requireAvailable;
|
||||||
|
case Supported:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
llvm_unreachable("Unhandled case in switch!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
namespace constraints {
|
namespace constraints {
|
||||||
|
|
||||||
struct ResultBuilder {
|
struct ResultBuilder {
|
||||||
@@ -115,7 +142,9 @@ private:
|
|||||||
/// An implicit variable that represents `Self` type of the result builder.
|
/// An implicit variable that represents `Self` type of the result builder.
|
||||||
VarDecl *BuilderSelf;
|
VarDecl *BuilderSelf;
|
||||||
Type BuilderType;
|
Type BuilderType;
|
||||||
llvm::SmallDenseMap<DeclName, bool> SupportedOps;
|
|
||||||
|
/// Cache of supported result builder operations.
|
||||||
|
llvm::SmallDenseMap<DeclName, ResultBuilderOpSupport> SupportedOps;
|
||||||
|
|
||||||
Identifier BuildOptionalId;
|
Identifier BuildOptionalId;
|
||||||
|
|
||||||
@@ -143,6 +172,13 @@ public:
|
|||||||
|
|
||||||
bool supportsOptional() { return supports(getBuildOptionalId()); }
|
bool supportsOptional() { return supports(getBuildOptionalId()); }
|
||||||
|
|
||||||
|
/// Checks whether the `buildPartialBlock` method is supported.
|
||||||
|
bool supportsBuildPartialBlock(bool checkAvailability);
|
||||||
|
|
||||||
|
/// Checks whether the builder can use `buildPartialBlock` to combine
|
||||||
|
/// expressions, instead of `buildBlock`.
|
||||||
|
bool canUseBuildPartialBlock();
|
||||||
|
|
||||||
Expr *buildCall(SourceLoc loc, Identifier fnName,
|
Expr *buildCall(SourceLoc loc, Identifier fnName,
|
||||||
ArrayRef<Expr *> argExprs,
|
ArrayRef<Expr *> argExprs,
|
||||||
ArrayRef<Identifier> argLabels) const;
|
ArrayRef<Identifier> argLabels) const;
|
||||||
|
|||||||
@@ -432,12 +432,7 @@ protected:
|
|||||||
// If the builder supports `buildPartialBlock(first:)` and
|
// If the builder supports `buildPartialBlock(first:)` and
|
||||||
// `buildPartialBlock(accumulated:next:)`, use this to combine
|
// `buildPartialBlock(accumulated:next:)`, use this to combine
|
||||||
// subexpressions pairwise.
|
// subexpressions pairwise.
|
||||||
if (!expressions.empty() &&
|
if (!expressions.empty() && builder.canUseBuildPartialBlock()) {
|
||||||
builder.supports(ctx.Id_buildPartialBlock, {ctx.Id_first},
|
|
||||||
/*checkAvailability*/ true) &&
|
|
||||||
builder.supports(ctx.Id_buildPartialBlock,
|
|
||||||
{ctx.Id_accumulated, ctx.Id_next},
|
|
||||||
/*checkAvailability*/ true)) {
|
|
||||||
// NOTE: The current implementation uses one-way constraints in between
|
// NOTE: The current implementation uses one-way constraints in between
|
||||||
// subexpressions. It's functionally equivalent to the following:
|
// subexpressions. It's functionally equivalent to the following:
|
||||||
// let v0 = Builder.buildPartialBlock(first: arg_0)
|
// let v0 = Builder.buildPartialBlock(first: arg_0)
|
||||||
@@ -1087,12 +1082,7 @@ protected:
|
|||||||
// If the builder supports `buildPartialBlock(first:)` and
|
// If the builder supports `buildPartialBlock(first:)` and
|
||||||
// `buildPartialBlock(accumulated:next:)`, use this to combine
|
// `buildPartialBlock(accumulated:next:)`, use this to combine
|
||||||
// sub-expressions pairwise.
|
// sub-expressions pairwise.
|
||||||
if (!buildBlockArguments.empty() &&
|
if (!buildBlockArguments.empty() && builder.canUseBuildPartialBlock()) {
|
||||||
builder.supports(ctx.Id_buildPartialBlock, {ctx.Id_first},
|
|
||||||
/*checkAvailability*/ true) &&
|
|
||||||
builder.supports(ctx.Id_buildPartialBlock,
|
|
||||||
{ctx.Id_accumulated, ctx.Id_next},
|
|
||||||
/*checkAvailability*/ true)) {
|
|
||||||
// let v0 = Builder.buildPartialBlock(first: arg_0)
|
// let v0 = Builder.buildPartialBlock(first: arg_0)
|
||||||
// let v1 = Builder.buildPartialBlock(accumulated: v0, next: arg_1)
|
// let v1 = Builder.buildPartialBlock(accumulated: v0, next: arg_1)
|
||||||
// ...
|
// ...
|
||||||
@@ -2777,11 +2767,22 @@ std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
|
|||||||
return precheck.getReturnStmts();
|
return precheck.getReturnStmts();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TypeChecker::typeSupportsBuilderOp(
|
ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
|
||||||
Type builderType, DeclContext *dc, Identifier fnName,
|
Type builderType, DeclContext *dc, Identifier fnName,
|
||||||
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults,
|
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
|
||||||
bool checkAvailability) {
|
|
||||||
|
auto isUnavailable = [&](Decl *D) -> bool {
|
||||||
|
if (AvailableAttr::isUnavailable(D))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto loc = extractNearestSourceLoc(dc);
|
||||||
|
auto context = ExportContext::forFunctionBody(dc, loc);
|
||||||
|
return TypeChecker::checkDeclarationAvailability(D, context).hasValue();
|
||||||
|
};
|
||||||
|
|
||||||
bool foundMatch = false;
|
bool foundMatch = false;
|
||||||
|
bool foundUnavailable = false;
|
||||||
|
|
||||||
SmallVector<ValueDecl *, 4> foundDecls;
|
SmallVector<ValueDecl *, 4> foundDecls;
|
||||||
dc->lookupQualified(
|
dc->lookupQualified(
|
||||||
builderType, DeclNameRef(fnName),
|
builderType, DeclNameRef(fnName),
|
||||||
@@ -2800,17 +2801,12 @@ bool TypeChecker::typeSupportsBuilderOp(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are checking availability, the candidate must have enough
|
// Check if the the candidate has a suitable availability for the
|
||||||
// availability in the calling context.
|
// calling context.
|
||||||
if (checkAvailability) {
|
if (isUnavailable(func)) {
|
||||||
if (AvailableAttr::isUnavailable(func))
|
foundUnavailable = true;
|
||||||
continue;
|
|
||||||
if (TypeChecker::checkDeclarationAvailability(
|
|
||||||
func, ExportContext::forFunctionBody(
|
|
||||||
dc, extractNearestSourceLoc(dc))))
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foundMatch = true;
|
foundMatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2819,7 +2815,24 @@ bool TypeChecker::typeSupportsBuilderOp(
|
|||||||
if (allResults)
|
if (allResults)
|
||||||
allResults->append(foundDecls.begin(), foundDecls.end());
|
allResults->append(foundDecls.begin(), foundDecls.end());
|
||||||
|
|
||||||
return foundMatch;
|
if (!foundMatch) {
|
||||||
|
return foundUnavailable ? ResultBuilderOpSupport::Unavailable
|
||||||
|
: ResultBuilderOpSupport::Unsupported;
|
||||||
|
}
|
||||||
|
// If the builder type itself isn't available, don't consider any builder
|
||||||
|
// method available.
|
||||||
|
if (auto *D = builderType->getAnyNominal()) {
|
||||||
|
if (isUnavailable(D))
|
||||||
|
return ResultBuilderOpSupport::Unavailable;
|
||||||
|
}
|
||||||
|
return ResultBuilderOpSupport::Supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TypeChecker::typeSupportsBuilderOp(
|
||||||
|
Type builderType, DeclContext *dc, Identifier fnName,
|
||||||
|
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
|
||||||
|
return checkBuilderOpSupport(builderType, dc, fnName, argLabels, allResults)
|
||||||
|
.isSupported(/*requireAvailable*/ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type swift::inferResultBuilderComponentType(NominalTypeDecl *builder) {
|
Type swift::inferResultBuilderComponentType(NominalTypeDecl *builder) {
|
||||||
@@ -2978,18 +2991,43 @@ ResultBuilder::ResultBuilder(ConstraintSystem *CS, DeclContext *DC,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ResultBuilder::supportsBuildPartialBlock(bool checkAvailability) {
|
||||||
|
auto &ctx = DC->getASTContext();
|
||||||
|
return supports(ctx.Id_buildPartialBlock, {ctx.Id_first},
|
||||||
|
checkAvailability) &&
|
||||||
|
supports(ctx.Id_buildPartialBlock, {ctx.Id_accumulated, ctx.Id_next},
|
||||||
|
checkAvailability);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResultBuilder::canUseBuildPartialBlock() {
|
||||||
|
// If buildPartialBlock doesn't exist at all, we can't use it.
|
||||||
|
if (!supportsBuildPartialBlock(/*checkAvailability*/ false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If buildPartialBlock exists and is available, use it.
|
||||||
|
if (supportsBuildPartialBlock(/*checkAvailability*/ true))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// We have buildPartialBlock, but it is unavailable. We can however still
|
||||||
|
// use it if buildBlock is also unavailable.
|
||||||
|
auto &ctx = DC->getASTContext();
|
||||||
|
return supports(ctx.Id_buildBlock) &&
|
||||||
|
!supports(ctx.Id_buildBlock, /*labels*/ {},
|
||||||
|
/*checkAvailability*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
bool ResultBuilder::supports(Identifier fnBaseName,
|
bool ResultBuilder::supports(Identifier fnBaseName,
|
||||||
ArrayRef<Identifier> argLabels,
|
ArrayRef<Identifier> argLabels,
|
||||||
bool checkAvailability) {
|
bool checkAvailability) {
|
||||||
DeclName name(DC->getASTContext(), fnBaseName, argLabels);
|
DeclName name(DC->getASTContext(), fnBaseName, argLabels);
|
||||||
auto known = SupportedOps.find(name);
|
auto known = SupportedOps.find(name);
|
||||||
if (known != SupportedOps.end()) {
|
if (known != SupportedOps.end())
|
||||||
return known->second;
|
return known->second.isSupported(checkAvailability);
|
||||||
}
|
|
||||||
|
|
||||||
return SupportedOps[name] = TypeChecker::typeSupportsBuilderOp(
|
auto support = TypeChecker::checkBuilderOpSupport(
|
||||||
BuilderType, DC, fnBaseName, argLabels, /*allResults*/ {},
|
BuilderType, DC, fnBaseName, argLabels, /*allResults*/ {});
|
||||||
checkAvailability);
|
SupportedOps.insert({name, support});
|
||||||
|
return support.isSupported(checkAvailability);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr *ResultBuilder::buildCall(SourceLoc loc, Identifier fnName,
|
Expr *ResultBuilder::buildCall(SourceLoc loc, Identifier fnName,
|
||||||
|
|||||||
@@ -1234,10 +1234,20 @@ UnresolvedMemberExpr *getUnresolvedMemberChainBase(Expr *expr);
|
|||||||
/// Checks whether a result builder type has a well-formed result builder
|
/// Checks whether a result builder type has a well-formed result builder
|
||||||
/// method with the given name. If provided and non-empty, the argument labels
|
/// method with the given name. If provided and non-empty, the argument labels
|
||||||
/// are verified against any candidates.
|
/// are verified against any candidates.
|
||||||
|
ResultBuilderOpSupport
|
||||||
|
checkBuilderOpSupport(Type builderType, DeclContext *dc, Identifier fnName,
|
||||||
|
ArrayRef<Identifier> argLabels = {},
|
||||||
|
SmallVectorImpl<ValueDecl *> *allResults = nullptr);
|
||||||
|
|
||||||
|
/// Checks whether a result builder type has a well-formed result builder
|
||||||
|
/// method with the given name. If provided and non-empty, the argument labels
|
||||||
|
/// are verified against any candidates.
|
||||||
|
///
|
||||||
|
/// This will return \c true even if the builder method is unavailable. Use
|
||||||
|
/// \c checkBuilderOpSupport if availability should be checked.
|
||||||
bool typeSupportsBuilderOp(Type builderType, DeclContext *dc, Identifier fnName,
|
bool typeSupportsBuilderOp(Type builderType, DeclContext *dc, Identifier fnName,
|
||||||
ArrayRef<Identifier> argLabels = {},
|
ArrayRef<Identifier> argLabels = {},
|
||||||
SmallVectorImpl<ValueDecl *> *allResults = nullptr,
|
SmallVectorImpl<ValueDecl *> *allResults = nullptr);
|
||||||
bool checkAvailability = false);
|
|
||||||
|
|
||||||
/// Forces all changes specified by the module's access notes file to be
|
/// Forces all changes specified by the module's access notes file to be
|
||||||
/// applied to this declaration. It is safe to call this function more than
|
/// applied to this declaration. It is safe to call this function more than
|
||||||
|
|||||||
@@ -167,3 +167,59 @@ tuplifyWithAvailabilityErasure(true) { cond in
|
|||||||
globalFuncAvailableOn10_52()
|
globalFuncAvailableOn10_52()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rdar://97533700 – Make sure we can prefer an unavailable buildPartialBlock if
|
||||||
|
// buildBlock also isn't available.
|
||||||
|
|
||||||
|
@resultBuilder
|
||||||
|
struct UnavailableBuildPartialBlock {
|
||||||
|
static func buildPartialBlock(first: Int) -> Int { 0 }
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
static func buildPartialBlock(accumulated: Int, next: Int) -> Int { 0 }
|
||||||
|
|
||||||
|
static func buildBlock(_ x: Int...) -> Int { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnavailableBuildPartialBlock
|
||||||
|
func testUnavailableBuildPartialBlock() -> Int {
|
||||||
|
// We can use buildBlock here.
|
||||||
|
2
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|
||||||
|
@resultBuilder
|
||||||
|
struct UnavailableBuildPartialBlockAndBuildBlock {
|
||||||
|
@available(*, unavailable)
|
||||||
|
static func buildPartialBlock(first: Int) -> Int { 0 }
|
||||||
|
// expected-note@-1 {{'buildPartialBlock(first:)' has been explicitly marked unavailable here}}
|
||||||
|
|
||||||
|
static func buildPartialBlock(accumulated: Int, next: Int) -> Int { 0 }
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
static func buildBlock(_ x: Int...) -> Int { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can still use buildPartialBlock here as both are unavailable.
|
||||||
|
@UnavailableBuildPartialBlockAndBuildBlock
|
||||||
|
func testUnavailableBuildPartialBlockAndBuildBlock() -> Int {
|
||||||
|
// expected-error@-1 {{'buildPartialBlock(first:)' is unavailable}}
|
||||||
|
2
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
@resultBuilder
|
||||||
|
struct UnavailableBuilderWithPartialBlock { // expected-note {{'UnavailableBuilderWithPartialBlock' has been explicitly marked unavailable here}}
|
||||||
|
@available(*, unavailable)
|
||||||
|
static func buildPartialBlock(first: String) -> Int { 0 }
|
||||||
|
static func buildPartialBlock(accumulated: Int, next: Int) -> Int { 0 }
|
||||||
|
static func buildBlock(_ x: Int...) -> Int { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnavailableBuilderWithPartialBlock // expected-error {{'UnavailableBuilderWithPartialBlock' is unavailable}}
|
||||||
|
func testUnavailableBuilderWithPartialBlock() -> Int {
|
||||||
|
// The builder itself is unavailable, so we can still opt for buildPartialBlock.
|
||||||
|
2 // expected-error {{cannot convert value of type 'Int' to expected argument type 'String'}}
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|||||||
28
test/StringProcessing/Sema/regex_builder_unavailable.swift
Normal file
28
test/StringProcessing/Sema/regex_builder_unavailable.swift
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift -enable-bare-slash-regex -target %target-cpu-apple-macosx12.0
|
||||||
|
|
||||||
|
// REQUIRES: OS=macosx
|
||||||
|
|
||||||
|
import RegexBuilder
|
||||||
|
|
||||||
|
// rdar://97533700 – Make sure we can emit an availability diagnostic here.
|
||||||
|
|
||||||
|
let _ = Regex { // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-error@-1 {{'init(_:)' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-note@-2 2{{add 'if #available' version check}}
|
||||||
|
|
||||||
|
Capture {} // expected-error {{'Capture' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-error@-1 {{'init(_:)' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-note@-2 2{{add 'if #available' version check}}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = Regex { // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-error@-1 {{'init(_:)' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-error@-2 {{'buildPartialBlock(accumulated:next:)' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-note@-3 3{{add 'if #available' version check}}
|
||||||
|
|
||||||
|
/abc/ // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-note@-1 {{add 'if #available' version check}}
|
||||||
|
|
||||||
|
/def/ // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
|
||||||
|
// expected-note@-1 {{add 'if #available' version check}}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user