Skip to content

Commit 80d949e

Browse files
committed
Diagnose inout uses of 'lets' in constructors in the type checker.
Stored `let` properties of a struct, class, or actor permit 'inout' modification within the constructor body after they have been initialized. Tentatively remove this rule, only allowing such `let` properties to be initialized (assigned to) and not treated as `inout`. Fixes rdar://127258363.
1 parent a34a2d3 commit 80d949e

15 files changed

+214
-86
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,6 +2092,14 @@ struct ClosureIsolatedByPreconcurrency {
20922092
bool operator()(const ClosureExpr *expr) const;
20932093
};
20942094

2095+
/// Determine whether the given expression is part of the left-hand side
2096+
/// of an assignment expression.
2097+
struct IsInLeftHandSideOfAssignment {
2098+
ConstraintSystem &cs;
2099+
2100+
bool operator()(Expr *expr) const;
2101+
};
2102+
20952103
/// Describes the type produced when referencing a declaration.
20962104
struct DeclReferenceType {
20972105
/// The "opened" type, which is the type of the declaration where any
@@ -4404,7 +4412,7 @@ class ConstraintSystem {
44044412
/// \param wantInterfaceType Whether we want the interface type, if available.
44054413
Type getUnopenedTypeOfReference(VarDecl *value, Type baseType,
44064414
DeclContext *UseDC,
4407-
ConstraintLocator *memberLocator = nullptr,
4415+
ConstraintLocator *locator,
44084416
bool wantInterfaceType = false,
44094417
bool adjustForPreconcurrency = true);
44104418

@@ -4426,7 +4434,7 @@ class ConstraintSystem {
44264434
getUnopenedTypeOfReference(
44274435
VarDecl *value, Type baseType, DeclContext *UseDC,
44284436
llvm::function_ref<Type(VarDecl *)> getType,
4429-
ConstraintLocator *memberLocator = nullptr,
4437+
ConstraintLocator *locator,
44304438
bool wantInterfaceType = false,
44314439
bool adjustForPreconcurrency = true,
44324440
llvm::function_ref<Type(const AbstractClosureExpr *)> getClosureType =
@@ -4436,7 +4444,10 @@ class ConstraintSystem {
44364444
llvm::function_ref<bool(const ClosureExpr *)> isolatedByPreconcurrency =
44374445
[](const ClosureExpr *closure) {
44384446
return closure->isIsolatedByPreconcurrency();
4439-
});
4447+
},
4448+
llvm::function_ref<bool(Expr *)> isAssignTarget = [](Expr *) {
4449+
return false;
4450+
});
44404451

44414452
/// Given the opened type and a pile of information about a member reference,
44424453
/// determine the reference type of the member reference.

lib/SILGen/SILGenConcurrency.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,8 @@ bool SILGenFunction::unsafelyInheritsExecutor() {
647647

648648
void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
649649
if (mustReturnToExecutor) {
650-
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor());
650+
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor() ||
651+
SGF.isCtorWithHopsInjectedByDefiniteInit());
651652
if (auto executor = SGF.ExpectedExecutor)
652653
SGF.B.createHopToExecutor(
653654
RegularLocation::getDebugOnlyLocation(loc, SGF.getModule()), executor,

lib/SILGen/SILGenConstructor.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,19 @@ static bool ctorHopsInjectedByDefiniteInit(ConstructorDecl *ctor,
618618
}
619619
}
620620

621+
bool SILGenFunction::isCtorWithHopsInjectedByDefiniteInit() {
622+
auto declRef = F.getDeclRef();
623+
if (!declRef || !declRef.isConstructor())
624+
return false;
625+
626+
auto ctor = dyn_cast_or_null<ConstructorDecl>(declRef.getDecl());
627+
if (!ctor)
628+
return false;
629+
630+
auto isolation = getActorIsolation(ctor);
631+
return ctorHopsInjectedByDefiniteInit(ctor, isolation);
632+
}
633+
621634
void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
622635
MagicFunctionName = SILGenModule::getMagicFunctionName(ctor);
623636

lib/SILGen/SILGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
820820
/// the fields.
821821
void emitDeallocatingMoveOnlyDestructor(DestructorDecl *dd);
822822

823+
/// Whether we are inside a constructor whose hops are injected by
824+
/// definite initialization.
825+
bool isCtorWithHopsInjectedByDefiniteInit();
826+
823827
/// Generates code for a struct constructor.
824828
/// This allocates the new 'self' value, emits the
825829
/// body code, then returns the final initialized 'self'.

lib/Sema/CSApply.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,12 +655,33 @@ namespace {
655655
}
656656

657657
auto ref = resolveConcreteDeclRef(decl, locator);
658+
659+
// If we have a variable that's treated as an rvalue but allows
660+
// assignment (for initialization) in the current context,
661+
// treat it as an rvalue that we immediately load. This is
662+
// the AST that's expected by SILGen.
663+
bool loadImmediately = false;
664+
if (auto var = dyn_cast_or_null<VarDecl>(ref.getDecl())) {
665+
if (!fullType->hasLValueType()) {
666+
if (var->mutability(dc) == StorageMutability::Initializable) {
667+
fullType = LValueType::get(fullType);
668+
adjustedFullType = LValueType::get(adjustedFullType);
669+
loadImmediately = true;
670+
}
671+
}
672+
}
673+
674+
658675
auto declRefExpr =
659676
new (ctx) DeclRefExpr(ref, loc, implicit, semantics, fullType);
660677
cs.cacheType(declRefExpr);
661678
declRefExpr->setFunctionRefKind(choice.getFunctionRefKind());
662679
Expr *result = adjustTypeForDeclReference(
663680
declRefExpr, fullType, adjustedFullType, locator);
681+
// If we have to load, do so now.
682+
if (loadImmediately)
683+
result = cs.addImplicitLoadExpr(result);
684+
664685
result = forceUnwrapIfExpected(result, locator);
665686

666687
if (auto *fnDecl = dyn_cast<AbstractFunctionDecl>(decl)) {

lib/Sema/ConstraintSystem.cpp

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,12 +1426,14 @@ ConstraintSystem::getWrappedPropertyInformation(
14261426
/// is being accessed; must be null if and only if this is not
14271427
/// a type member
14281428
static bool
1429-
doesStorageProduceLValue(AbstractStorageDecl *storage, Type baseType,
1430-
DeclContext *useDC,
1431-
ConstraintLocator *memberLocator = nullptr) {
1429+
doesStorageProduceLValue(
1430+
AbstractStorageDecl *storage, Type baseType,
1431+
DeclContext *useDC,
1432+
llvm::function_ref<bool(Expr *)> isAssignTarget,
1433+
ConstraintLocator *locator) {
14321434
const DeclRefExpr *base = nullptr;
1433-
if (memberLocator) {
1434-
if (auto *const E = getAsExpr(memberLocator->getAnchor())) {
1435+
if (locator) {
1436+
if (auto *const E = getAsExpr(locator->getAnchor())) {
14351437
if (auto *MRE = dyn_cast<MemberRefExpr>(E)) {
14361438
base = dyn_cast<DeclRefExpr>(MRE->getBase());
14371439
} else if (auto *UDE = dyn_cast<UnresolvedDotExpr>(E)) {
@@ -1440,9 +1442,33 @@ doesStorageProduceLValue(AbstractStorageDecl *storage, Type baseType,
14401442
}
14411443
}
14421444

1443-
// Unsettable storage decls always produce rvalues.
1444-
if (!storage->isSettableInSwift(useDC, base))
1445-
return false;
1445+
switch (storage->mutabilityInSwift(useDC, base)) {
1446+
case StorageMutability::Immutable:
1447+
// Immutable storage decls always produce rvalues.
1448+
return false;
1449+
1450+
case StorageMutability::Mutable:
1451+
break;
1452+
1453+
case StorageMutability::Initializable: {
1454+
// If the member is not known to be on the left-hand side of
1455+
// an assignment, treat it as an rvalue so we can't pass it
1456+
// inout.
1457+
if (!locator)
1458+
return false;
1459+
1460+
auto *anchor = getAsExpr(simplifyLocatorToAnchor(locator));
1461+
if (!anchor)
1462+
return false;
1463+
1464+
// If the anchor isn't known to be the target of an assignment,
1465+
// treat as immutable.
1466+
if (!isAssignTarget(anchor))
1467+
return false;
1468+
1469+
break;
1470+
}
1471+
}
14461472

14471473
if (!storage->isSetterAccessibleFrom(useDC))
14481474
return false;
@@ -1482,7 +1508,7 @@ ClosureIsolatedByPreconcurrency::operator()(const ClosureExpr *expr) const {
14821508

14831509
Type ConstraintSystem::getUnopenedTypeOfReference(
14841510
VarDecl *value, Type baseType, DeclContext *UseDC,
1485-
ConstraintLocator *memberLocator, bool wantInterfaceType,
1511+
ConstraintLocator *locator, bool wantInterfaceType,
14861512
bool adjustForPreconcurrency) {
14871513
return ConstraintSystem::getUnopenedTypeOfReference(
14881514
value, baseType, UseDC,
@@ -1496,18 +1522,20 @@ Type ConstraintSystem::getUnopenedTypeOfReference(
14961522

14971523
return wantInterfaceType ? var->getInterfaceType() : var->getTypeInContext();
14981524
},
1499-
memberLocator, wantInterfaceType, adjustForPreconcurrency,
1525+
locator, wantInterfaceType, adjustForPreconcurrency,
15001526
GetClosureType{*this},
1501-
ClosureIsolatedByPreconcurrency{*this});
1527+
ClosureIsolatedByPreconcurrency{*this},
1528+
IsInLeftHandSideOfAssignment{*this});
15021529
}
15031530

15041531
Type ConstraintSystem::getUnopenedTypeOfReference(
15051532
VarDecl *value, Type baseType, DeclContext *UseDC,
15061533
llvm::function_ref<Type(VarDecl *)> getType,
1507-
ConstraintLocator *memberLocator,
1534+
ConstraintLocator *locator,
15081535
bool wantInterfaceType, bool adjustForPreconcurrency,
15091536
llvm::function_ref<Type(const AbstractClosureExpr *)> getClosureType,
1510-
llvm::function_ref<bool(const ClosureExpr *)> isolatedByPreconcurrency) {
1537+
llvm::function_ref<bool(const ClosureExpr *)> isolatedByPreconcurrency,
1538+
llvm::function_ref<bool(Expr *)> isAssignTarget) {
15111539
Type requestedType =
15121540
getType(value)->getWithoutSpecifierType()->getReferenceStorageReferent();
15131541

@@ -1532,7 +1560,8 @@ Type ConstraintSystem::getUnopenedTypeOfReference(
15321560

15331561
// Qualify storage declarations with an lvalue when appropriate.
15341562
// Otherwise, they yield rvalues (and the access must be a load).
1535-
if (doesStorageProduceLValue(value, baseType, UseDC, memberLocator) &&
1563+
if (doesStorageProduceLValue(value, baseType, UseDC, isAssignTarget,
1564+
locator) &&
15361565
!requestedType->hasError()) {
15371566
return LValueType::get(requestedType);
15381567
}
@@ -1905,7 +1934,8 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
19051934
// FIXME: @preconcurrency
19061935
bool wantInterfaceType = !varDecl->getDeclContext()->isLocalContext();
19071936
Type valueType =
1908-
getUnopenedTypeOfReference(varDecl, Type(), useDC, /*base=*/nullptr,
1937+
getUnopenedTypeOfReference(varDecl, Type(), useDC,
1938+
getConstraintLocator(locator),
19091939
wantInterfaceType);
19101940

19111941
Type thrownErrorType;
@@ -2579,6 +2609,48 @@ bool ConstraintSystem::isPartialApplication(ConstraintLocator *locator) {
25792609
return level < (baseTy->is<MetatypeType>() ? 1 : 2);
25802610
}
25812611

2612+
bool IsInLeftHandSideOfAssignment::operator()(Expr *expr) const {
2613+
// Walk up the parent tree.
2614+
auto parent = cs.getParentExpr(expr);
2615+
if (!parent)
2616+
return false;
2617+
2618+
// If the parent is an assignment expression, check whether we are
2619+
// the left-hand side.
2620+
if (auto assignParent = dyn_cast<AssignExpr>(parent)) {
2621+
return expr == assignParent->getDest();
2622+
}
2623+
2624+
// Always look up through these parent kinds.
2625+
if (isa<TupleExpr>(parent) || isa<IdentityExpr>(parent) ||
2626+
isa<AnyTryExpr>(parent) || isa<BindOptionalExpr>(parent)) {
2627+
return (*this)(parent);
2628+
}
2629+
2630+
// If the parent is a lookup expression, only follow from the base.
2631+
if (auto lookupParent = dyn_cast<LookupExpr>(parent)) {
2632+
if (lookupParent->getBase() == expr)
2633+
return (*this)(parent);
2634+
2635+
// The expression wasn't in the base, so this isn't part of the
2636+
// left-hand side.
2637+
return false;
2638+
}
2639+
2640+
// If the parent is an unresolved member reference a.b, only follow
2641+
// from the base.
2642+
if (auto dotParent = dyn_cast<UnresolvedDotExpr>(parent)) {
2643+
if (dotParent->getBase() == expr)
2644+
return (*this)(parent);
2645+
2646+
// The expression wasn't in the base, so this isn't part of the
2647+
// left-hand side.
2648+
return false;
2649+
}
2650+
2651+
return false;
2652+
}
2653+
25822654
DeclReferenceType
25832655
ConstraintSystem::getTypeOfMemberReference(
25842656
Type baseTy, ValueDecl *value, DeclContext *useDC,
@@ -2684,7 +2756,9 @@ ConstraintSystem::getTypeOfMemberReference(
26842756
if (auto *subscript = dyn_cast<SubscriptDecl>(value)) {
26852757
auto elementTy = subscript->getElementInterfaceType();
26862758

2687-
if (doesStorageProduceLValue(subscript, baseTy, useDC, locator))
2759+
if (doesStorageProduceLValue(
2760+
subscript, baseTy, useDC,
2761+
IsInLeftHandSideOfAssignment{*this}, locator))
26882762
elementTy = LValueType::get(elementTy);
26892763

26902764
auto indices = subscript->getInterfaceType()
@@ -2939,7 +3013,10 @@ Type ConstraintSystem::getEffectiveOverloadType(ConstraintLocator *locator,
29393013
if (auto subscript = dyn_cast<SubscriptDecl>(decl)) {
29403014
auto elementTy = subscript->getElementInterfaceType();
29413015

2942-
if (doesStorageProduceLValue(subscript, overload.getBaseType(), useDC))
3016+
if (doesStorageProduceLValue(subscript, overload.getBaseType(),
3017+
useDC,
3018+
IsInLeftHandSideOfAssignment{*this},
3019+
locator))
29433020
elementTy = LValueType::get(elementTy);
29443021
else if (elementTy->hasDynamicSelfType()) {
29453022
elementTy = withDynamicSelfResultReplaced(elementTy,
@@ -2963,7 +3040,9 @@ Type ConstraintSystem::getEffectiveOverloadType(ConstraintLocator *locator,
29633040
locator);
29643041
} else if (auto var = dyn_cast<VarDecl>(decl)) {
29653042
type = var->getValueInterfaceType();
2966-
if (doesStorageProduceLValue(var, overload.getBaseType(), useDC)) {
3043+
if (doesStorageProduceLValue(
3044+
var, overload.getBaseType(), useDC,
3045+
IsInLeftHandSideOfAssignment{*this}, locator)) {
29673046
type = LValueType::get(type);
29683047
} else if (type->hasDynamicSelfType()) {
29693048
type = withDynamicSelfResultReplaced(type, /*uncurryLevel=*/0);

lib/Sema/TypeCheckExpr.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -581,17 +581,6 @@ bool TypeChecker::requireArrayLiteralIntrinsics(ASTContext &ctx,
581581
return true;
582582
}
583583

584-
Expr *TypeChecker::buildCheckedRefExpr(VarDecl *value, DeclContext *UseDC,
585-
DeclNameLoc loc, bool Implicit) {
586-
auto type = constraints::ConstraintSystem::getUnopenedTypeOfReference(
587-
value, Type(), UseDC,
588-
[&](VarDecl *var) -> Type { return value->getTypeInContext(); });
589-
auto semantics = value->getAccessSemanticsFromContext(UseDC,
590-
/*isAccessOnSelf*/false);
591-
return new (value->getASTContext())
592-
DeclRefExpr(value, loc, Implicit, semantics, type);
593-
}
594-
595584
Expr *TypeChecker::buildRefExpr(ArrayRef<ValueDecl *> Decls,
596585
DeclContext *UseDC, DeclNameLoc NameLoc,
597586
bool Implicit, FunctionRefKind functionRefKind) {

lib/Sema/TypeChecker.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -944,10 +944,6 @@ Comparison compareDeclarations(DeclContext *dc, ValueDecl *decl1,
944944
/// of the first one and the expression will still type check.
945945
bool isDeclRefinementOf(ValueDecl *declA, ValueDecl *declB);
946946

947-
/// Build a type-checked reference to the given value.
948-
Expr *buildCheckedRefExpr(VarDecl *D, DeclContext *UseDC, DeclNameLoc nameLoc,
949-
bool Implicit);
950-
951947
/// Build a reference to a declaration, where name lookup returned
952948
/// the given set of declarations.
953949
Expr *buildRefExpr(ArrayRef<ValueDecl *> Decls, DeclContext *UseDC,

test/Constraints/diagnostics.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,9 @@ do {
13961396
func generic<T>(_ value: inout T, _ closure: (S<T>) -> Void) {}
13971397

13981398
let arg: Int
1399+
// expected-note@-1{{change 'let' to 'var' to make it mutable}}
13991400
generic(&arg) { (g: S<Double>) -> Void in } // expected-error {{cannot convert value of type '(S<Double>) -> Void' to expected argument type '(S<Int>) -> Void'}}
1401+
// expected-error@-1{{cannot pass immutable value as inout argument: 'arg' is a 'let' constant}}
14001402
}
14011403

14021404
// rdar://problem/62428353 - bad error message for passing `T` where `inout T` was expected
@@ -1556,18 +1558,17 @@ func testNilCoalescingOperatorRemoveFix() {
15561558
let _ = "" /* This is a comment */ ?? "" // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{13-43=}}
15571559

15581560
let _ = "" // This is a comment
1559-
?? "" // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{1558:13-1559:10=}}
1561+
?? "" // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{1560:13-1561:10=}}
15601562

15611563
let _ = "" // This is a comment
15621564
/*
15631565
* The blank line below is part of the test case, do not delete it
15641566
*/
1567+
?? "" // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{1563:13-1567:10=}}
15651568

1566-
?? "" // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{1561:13-1566:10=}}
1567-
1568-
if ("" ?? // This is a comment // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{9-1569:9=}}
1569+
if ("" ?? // This is a comment // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{9-1570:9=}}
15691570
"").isEmpty {}
15701571

15711572
if ("" // This is a comment
1572-
?? "").isEmpty {} // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{1571:9-1572:12=}}
1573+
?? "").isEmpty {} // expected-warning {{left side of nil coalescing operator '??' has non-optional type 'String', so the right side is never used}} {{1572:9-1573:12=}}
15731574
}

0 commit comments

Comments
 (0)