Merge pull request #60803 from hamishknight/part-and-partial

This commit is contained in:
Hamish Knight
2022-08-31 14:57:53 +01:00
committed by GitHub
5 changed files with 203 additions and 35 deletions

View File

@@ -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;

View File

@@ -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; continue;
if (TypeChecker::checkDeclarationAvailability(
func, ExportContext::forFunctionBody(
dc, extractNearestSourceLoc(dc))))
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,

View File

@@ -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

View File

@@ -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
}

View 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}}
}