Consume: warn about no-op consumes to be fixed

As of now, SE-366 is not correctly implemented with respect to concrete,
bitwise-copyable types like `Int`. Writing `consume someInt` doesn't
actually consume the variable binding as it should, meaning code that
should be flagged as having a use-after-consume is being silently
permitted:

```swift
let someInt = 10
let y = consume someInt
print(someInt) // no error!
```

This has been a problem since Swift 5.9. Eventually we plan to fix this
issue, which means code previously doing the above would become an
error. To help people get ready for the fix, start warning people that
these consumes are actually no-ops and suggest removing them until the
intended behavior is actually enforced in the future.

resolves rdar://127081103
This commit is contained in:
Kavon Farvardin
2024-07-24 13:28:42 -07:00
parent 5230b19ef6
commit e04b35d709
3 changed files with 112 additions and 0 deletions

View File

@@ -7749,6 +7749,8 @@ ERROR(consume_expression_needed_for_cast,none,
"implicit conversion to %0 is consuming", (Type))
NOTE(add_consume_to_silence,none,
"add 'consume' to make consumption explicit", ())
WARNING(consume_of_bitwisecopyable_noop,none,
"'consume' applied to bitwise-copyable type %0 has no effect", (Type))
ERROR(consume_expression_not_passed_lvalue,none,
"'consume' can only be applied to a local binding ('let', 'var', or parameter)", ())
ERROR(consume_expression_partial_copyable,none,

View File

@@ -437,6 +437,56 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
consumeExpr->getSubExpr());
for (auto &diag : diags)
diag.emit(Ctx);
// As of now, SE-366 is not correctly implemented (rdar://102780553),
// so warn about certain consume's being no-ops today that will no longer
// be a no-op in the future once we fix this.
if (auto ty = consumeExpr->getType()) {
bool shouldWarn = true;
// Look through any load.
auto *expr = consumeExpr->getSubExpr();
if (auto *load = dyn_cast<LoadExpr>(expr))
expr = load->getSubExpr();
// Don't warn if explicit ownership was provided on a parameter.
// Those seem to be checked just fine in SIL.
if (auto *declRef = dyn_cast<DeclRefExpr>(expr)) {
if (auto *decl = declRef->getDecl()) {
if (auto *paramDecl = dyn_cast<ParamDecl>(decl)) {
switch (paramDecl->getSpecifier()) {
case ParamSpecifier::InOut:
case ParamSpecifier::Borrowing:
case ParamSpecifier::Consuming:
case ParamSpecifier::ImplicitlyCopyableConsuming:
shouldWarn = false;
break;
case ParamSpecifier::Default:
case ParamSpecifier::LegacyShared:
case ParamSpecifier::LegacyOwned:
break; // warn
}
}
}
}
// Only warn about obviously concrete BitwiseCopyable types, since we
// know those won't get checked for consumption.
if (diags.empty() &&
shouldWarn &&
!ty->hasError() &&
!ty->hasTypeParameter() &&
!ty->hasUnboundGenericType() &&
!ty->hasArchetype()) {
auto bitCopy = Ctx.getProtocol(KnownProtocolKind::BitwiseCopyable);
if (DC->getParentModule()->checkConformance(ty, bitCopy)) {
Ctx.Diags.diagnose(consumeExpr->getLoc(),
diag::consume_of_bitwisecopyable_noop, ty)
.fixItRemoveChars(consumeExpr->getStartLoc(),
consumeExpr->getSubExpr()->getStartLoc());
}
}
}
}
void checkCopyExpr(CopyExpr *copyExpr) {

View File

@@ -0,0 +1,60 @@
// RUN: %target-swift-emit-sil %s -verify -sil-verify-all
struct Point {
let x: Float
let y: Float
}
struct ConditionallyBC<T> {
var t: T
}
extension ConditionallyBC: BitwiseCopyable where T: BitwiseCopyable {}
func test<T, BCG: BitwiseCopyable>(_ t: T, // expected-error {{'t' is borrowed and cannot be consumed}}
_ bcg: BCG, // expected-error {{'bcg' is borrowed and cannot be consumed}}
_ cbcg_generic: ConditionallyBC<BCG>, // expected-error {{'cbcg_generic' is borrowed and cannot be consumed}}
_ maybeBCG: BCG?, // expected-error {{'maybeBCG' is borrowed and cannot be consumed}}
_ maybeT: T?, // expected-error {{'maybeT' is borrowed and cannot be consumed}}
_ anyBC: any BitwiseCopyable, // expected-error {{'anyBC' is borrowed and cannot be consumed}}
_ x: Int,
_ point: Point,
_ cbcg_concrete: ConditionallyBC<Int>,
_ maybeFloat: Float?) {
_ = consume t // expected-note {{consumed here}}
_ = consume bcg // expected-note {{consumed here}}
_ = consume cbcg_generic // expected-note {{consumed here}}
_ = consume maybeBCG // expected-note {{consumed here}}
_ = consume maybeT // expected-note {{consumed here}}
_ = consume anyBC // expected-note {{consumed here}}
_ = consume x // expected-warning {{'consume' applied to bitwise-copyable type 'Int' has no effect}}{{7-15=}}
_ = consume point // expected-warning {{'consume' applied to bitwise-copyable type 'Point' has no effect}}{{7-15=}}
_ = consume cbcg_concrete // expected-warning {{'consume' applied to bitwise-copyable type 'ConditionallyBC<Int>' has no effect}}{{7-16=}}
_ = consume maybeFloat // expected-warning {{'consume' applied to bitwise-copyable type 'Float?' has no effect}}{{8-16=}}
}
func proofOfUseAfterConsume() -> Int {
let someInt = 10
let y = consume someInt // expected-warning {{'consume' applied to bitwise-copyable type 'Int' has no effect}}
print(y)
return someInt // undiagnosed use-after-consume
}
func moreProofs(_ share: __shared Int,
_ own: __owned Int,
_ snd: sending Int, // expected-error {{'snd' used after consume}}
_ ino: inout Int, // expected-error {{'ino' used after consume}}
_ brw: borrowing Int, // expected-error {{'brw' is borrowed and cannot be consumed}}
_ csm: consuming Int // expected-error {{'csm' consumed more than once}}
) {
_ = consume share // expected-warning {{'consume' applied to bitwise-copyable type 'Int' has no effect}}
_ = consume own // expected-warning {{'consume' applied to bitwise-copyable type 'Int' has no effect}}
let _ = (share, own)
_ = consume ino // expected-note {{consumed}}
_ = consume brw // expected-note {{consumed}}
_ = consume csm // expected-note {{consumed}}
_ = consume csm // expected-note {{consumed}}
_ = consume snd // expected-note {{consumed}}
_ = snd // expected-note {{used}}
} // expected-note {{used here}}