Skip to content

Commit facd27f

Browse files
authored
Merge pull request swiftlang#26912 from xedin/allow-inout-conversion-fix
[ConstraintSystem] Introduce a fix to allow conversion between `inout` types
2 parents c073e62 + b9cf4fa commit facd27f

File tree

8 files changed

+147
-90
lines changed

8 files changed

+147
-90
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,15 +1668,6 @@ bool FailureDiagnosis::diagnoseContextualConversionError(
16681668
if (!exprType)
16691669
return CS.TC.Diags.hadAnyError();
16701670

1671-
// If we contextually had an inout type, and got a non-lvalue result, then
1672-
// we fail with a mutability error.
1673-
if (contextualType->is<InOutType>() && !exprType->is<LValueType>()) {
1674-
AssignmentFailure failure(recheckedExpr, CS, recheckedExpr->getLoc(),
1675-
diag::cannot_pass_rvalue_inout_subelement,
1676-
diag::cannot_pass_rvalue_inout);
1677-
return failure.diagnose();
1678-
}
1679-
16801671
// If we don't have a type for the expression, then we cannot use it in
16811672
// conversion constraint diagnostic generation. If the types match, then it
16821673
// must not be the contextual type that is the problem.

lib/Sema/CSDiagnostics.cpp

Lines changed: 77 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,17 +1567,7 @@ bool AssignmentFailure::diagnoseAsError() {
15671567
Type neededType = contextualType->getInOutObjectType();
15681568
Type actualType = getType(immInfo.first)->getInOutObjectType();
15691569
if (!neededType->isEqual(actualType)) {
1570-
if (DeclDiagnostic.ID == diag::cannot_pass_rvalue_inout_subelement.ID) {
1571-
// We have a special diagnostic with tailored wording for this
1572-
// common case.
1573-
emitDiagnostic(Loc, diag::cannot_pass_rvalue_inout_converted,
1574-
actualType, neededType)
1575-
.highlight(immInfo.first->getSourceRange());
1576-
1577-
if (auto inoutExpr = dyn_cast<InOutExpr>(immInfo.first))
1578-
fixItChangeInoutArgType(inoutExpr->getSubExpr(), actualType,
1579-
neededType);
1580-
} else {
1570+
if (DeclDiagnostic.ID != diag::cannot_pass_rvalue_inout_subelement.ID) {
15811571
emitDiagnostic(Loc, DeclDiagnostic,
15821572
"implicit conversion from '" + actualType->getString() +
15831573
"' to '" + neededType->getString() +
@@ -1601,68 +1591,6 @@ bool AssignmentFailure::diagnoseAsError() {
16011591
return true;
16021592
}
16031593

1604-
void AssignmentFailure::fixItChangeInoutArgType(const Expr *arg,
1605-
Type actualType,
1606-
Type neededType) const {
1607-
auto *DC = getDC();
1608-
auto *DRE = dyn_cast<DeclRefExpr>(arg);
1609-
if (!DRE)
1610-
return;
1611-
1612-
auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl());
1613-
if (!VD)
1614-
return;
1615-
1616-
// Don't emit for non-local variables.
1617-
// (But in script-mode files, we consider module-scoped
1618-
// variables in the same file to be local variables.)
1619-
auto VDC = VD->getDeclContext();
1620-
bool isLocalVar = VDC->isLocalContext();
1621-
if (!isLocalVar && VDC->isModuleScopeContext()) {
1622-
auto argFile = DC->getParentSourceFile();
1623-
auto varFile = VDC->getParentSourceFile();
1624-
isLocalVar = (argFile == varFile && argFile->isScriptMode());
1625-
}
1626-
if (!isLocalVar)
1627-
return;
1628-
1629-
SmallString<32> scratch;
1630-
SourceLoc endLoc; // Filled in if we decide to diagnose this
1631-
SourceLoc startLoc; // Left invalid if we're inserting
1632-
1633-
auto isSimpleTypelessPattern = [](Pattern *P) -> bool {
1634-
if (auto VP = dyn_cast_or_null<VarPattern>(P))
1635-
P = VP->getSubPattern();
1636-
return P && isa<NamedPattern>(P);
1637-
};
1638-
1639-
auto typeRange = VD->getTypeSourceRangeForDiagnostics();
1640-
if (typeRange.isValid()) {
1641-
startLoc = typeRange.Start;
1642-
endLoc = typeRange.End;
1643-
} else if (isSimpleTypelessPattern(VD->getParentPattern())) {
1644-
endLoc = VD->getNameLoc();
1645-
scratch += ": ";
1646-
}
1647-
1648-
if (endLoc.isInvalid())
1649-
return;
1650-
1651-
scratch += neededType.getString();
1652-
1653-
// Adjust into the location where we actually want to insert
1654-
endLoc = Lexer::getLocForEndOfToken(getASTContext().SourceMgr, endLoc);
1655-
1656-
// Since we already adjusted endLoc, this will turn an insertion
1657-
// into a zero-character replacement.
1658-
if (!startLoc.isValid())
1659-
startLoc = endLoc;
1660-
1661-
emitDiagnostic(VD->getLoc(), diag::inout_change_var_type_if_possible,
1662-
actualType, neededType)
1663-
.fixItReplaceChars(startLoc, endLoc, scratch);
1664-
}
1665-
16661594
std::pair<Expr *, Optional<OverloadChoice>>
16671595
AssignmentFailure::resolveImmutableBase(Expr *expr) const {
16681596
auto &cs = getConstraintSystem();
@@ -4348,3 +4276,79 @@ bool ThrowingFunctionConversionFailure::diagnoseAsError() {
43484276
getFromType(), getToType());
43494277
return true;
43504278
}
4279+
4280+
bool InOutConversionFailure::diagnoseAsError() {
4281+
auto *anchor = getAnchor();
4282+
emitDiagnostic(anchor->getLoc(), diag::cannot_pass_rvalue_inout_converted,
4283+
getFromType(), getToType());
4284+
fixItChangeArgumentType();
4285+
return true;
4286+
}
4287+
4288+
void InOutConversionFailure::fixItChangeArgumentType() const {
4289+
auto *argExpr = getAnchor();
4290+
auto *DC = getDC();
4291+
4292+
if (auto *IOE = dyn_cast<InOutExpr>(argExpr))
4293+
argExpr = IOE->getSubExpr();
4294+
4295+
auto *DRE = dyn_cast<DeclRefExpr>(argExpr);
4296+
if (!DRE)
4297+
return;
4298+
4299+
auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl());
4300+
if (!VD)
4301+
return;
4302+
4303+
// Don't emit for non-local variables.
4304+
// (But in script-mode files, we consider module-scoped
4305+
// variables in the same file to be local variables.)
4306+
auto VDC = VD->getDeclContext();
4307+
bool isLocalVar = VDC->isLocalContext();
4308+
if (!isLocalVar && VDC->isModuleScopeContext()) {
4309+
auto argFile = DC->getParentSourceFile();
4310+
auto varFile = VDC->getParentSourceFile();
4311+
isLocalVar = (argFile == varFile && argFile->isScriptMode());
4312+
}
4313+
if (!isLocalVar)
4314+
return;
4315+
4316+
auto actualType = getFromType();
4317+
auto neededType = getToType();
4318+
4319+
SmallString<32> scratch;
4320+
SourceLoc endLoc; // Filled in if we decide to diagnose this
4321+
SourceLoc startLoc; // Left invalid if we're inserting
4322+
4323+
auto isSimpleTypelessPattern = [](Pattern *P) -> bool {
4324+
if (auto VP = dyn_cast_or_null<VarPattern>(P))
4325+
P = VP->getSubPattern();
4326+
return P && isa<NamedPattern>(P);
4327+
};
4328+
4329+
auto typeRange = VD->getTypeSourceRangeForDiagnostics();
4330+
if (typeRange.isValid()) {
4331+
startLoc = typeRange.Start;
4332+
endLoc = typeRange.End;
4333+
} else if (isSimpleTypelessPattern(VD->getParentPattern())) {
4334+
endLoc = VD->getNameLoc();
4335+
scratch += ": ";
4336+
}
4337+
4338+
if (endLoc.isInvalid())
4339+
return;
4340+
4341+
scratch += neededType.getString();
4342+
4343+
// Adjust into the location where we actually want to insert
4344+
endLoc = Lexer::getLocForEndOfToken(getASTContext().SourceMgr, endLoc);
4345+
4346+
// Since we already adjusted endLoc, this will turn an insertion
4347+
// into a zero-character replacement.
4348+
if (!startLoc.isValid())
4349+
startLoc = endLoc;
4350+
4351+
emitDiagnostic(VD->getLoc(), diag::inout_change_var_type_if_possible,
4352+
actualType, neededType)
4353+
.fixItReplaceChars(startLoc, endLoc, scratch);
4354+
}

lib/Sema/CSDiagnostics.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -574,9 +574,6 @@ class AssignmentFailure final : public FailureDiagnostic {
574574
bool diagnoseAsError() override;
575575

576576
private:
577-
void fixItChangeInoutArgType(const Expr *arg, Type actualType,
578-
Type neededType) const;
579-
580577
/// Given an expression that has a non-lvalue type, dig into it until
581578
/// we find the part of the expression that prevents the entire subexpression
582579
/// from being mutable. For example, in a sequence like "x.v.v = 42" we want
@@ -1509,6 +1506,24 @@ class MissingContextualConformanceFailure final : public ContextualFailure {
15091506
bool diagnoseAsError() override;
15101507
};
15111508

1509+
/// Diagnose a conversion mismatch between object types of `inout`
1510+
/// argument/parameter e.g. `'inout S' argument conv 'inout P'`.
1511+
///
1512+
/// Even if `S` conforms to `P` there is no subtyping rule for
1513+
/// argument type of `inout` parameter, they have to be equal.
1514+
class InOutConversionFailure final : public ContextualFailure {
1515+
public:
1516+
InOutConversionFailure(Expr *root, ConstraintSystem &cs, Type argType,
1517+
Type paramType, ConstraintLocator *locator)
1518+
: ContextualFailure(root, cs, argType, paramType, locator) {}
1519+
1520+
bool diagnoseAsError() override;
1521+
1522+
protected:
1523+
/// Suggest to change a type of the argument if possible.
1524+
void fixItChangeArgumentType() const;
1525+
};
1526+
15121527
/// Diagnose generic argument omission e.g.
15131528
///
15141529
/// ```swift

lib/Sema/CSFix.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,3 +773,17 @@ IgnoreContextualType *IgnoreContextualType::create(ConstraintSystem &cs,
773773
return new (cs.getAllocator())
774774
IgnoreContextualType(cs, resultTy, specifiedTy, locator);
775775
}
776+
777+
bool AllowInOutConversion::diagnose(Expr *root, bool asNote) const {
778+
auto &cs = getConstraintSystem();
779+
InOutConversionFailure failure(root, cs, getFromType(), getToType(),
780+
getLocator());
781+
return failure.diagnose(asNote);
782+
}
783+
784+
AllowInOutConversion *AllowInOutConversion::create(ConstraintSystem &cs,
785+
Type argType, Type paramType,
786+
ConstraintLocator *locator) {
787+
return new (cs.getAllocator())
788+
AllowInOutConversion(cs, argType, paramType, locator);
789+
}

lib/Sema/CSFix.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,26 @@ class IgnoreContextualType : public ContextualMismatch {
13121312
ConstraintLocator *locator);
13131313
};
13141314

1315+
/// If this is an argument-to-parameter conversion which is associated with
1316+
/// `inout` parameter, subtyping is now permitted, types have to
1317+
/// be identical.
1318+
class AllowInOutConversion final : public ContextualMismatch {
1319+
AllowInOutConversion(ConstraintSystem &cs, Type argType, Type paramType,
1320+
ConstraintLocator *locator)
1321+
: ContextualMismatch(cs, argType, paramType, locator) {}
1322+
1323+
public:
1324+
std::string getName() const override {
1325+
return "allow conversions between argument/parameter marked as `inout`";
1326+
}
1327+
1328+
bool diagnose(Expr *root, bool asNote = false) const override;
1329+
1330+
static AllowInOutConversion *create(ConstraintSystem &cs, Type argType,
1331+
Type paramType,
1332+
ConstraintLocator *locator);
1333+
};
1334+
13151335
} // end namespace constraints
13161336
} // end namespace swift
13171337

lib/Sema/CSSimplify.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,19 @@ bool ConstraintSystem::repairFailures(
25122512
if (lhs->getOptionalObjectType() && !rhs->getOptionalObjectType()) {
25132513
conversionsOrFixes.push_back(
25142514
ForceOptional::create(*this, lhs, lhs->getOptionalObjectType(), loc));
2515+
break;
2516+
}
2517+
2518+
// There is no subtyping between object types of inout argument/parameter.
2519+
if (elt.getKind() == ConstraintLocator::LValueConversion) {
2520+
auto result = matchTypes(lhs, rhs, ConstraintKind::Conversion,
2521+
TMF_ApplyingFix, locator);
2522+
2523+
if (!result.isFailure()) {
2524+
conversionsOrFixes.push_back(
2525+
AllowInOutConversion::create(*this, lhs, rhs, loc));
2526+
break;
2527+
}
25152528
}
25162529

25172530
if (elt.getKind() != ConstraintLocator::ApplyArgToParam)

test/Constraints/lvalues.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ z[0] = 0.0 // expected-error{{cannot assign through subscript: subscript is get-
9090
f2(&z[0]) // expected-error{{cannot pass immutable value as inout argument: subscript is get-only}}
9191
f1(&z[0]) // expected-error{{cannot pass immutable value as inout argument: subscript is get-only}}
9292
z[0] += 0.0 // expected-error{{left side of mutating operator isn't mutable: subscript is get-only}}
93-
+++z[0] // expected-error{{cannot pass immutable value as inout argument: subscript is get-only}}
93+
+++z[0] // expected-error{{cannot convert value of type 'Double' to expected argument type 'X'}}
94+
+++z[(i: 0, j: 0)] // expected-error{{cannot pass immutable value to mutating operator: subscript is get-only}}
9495

9596
// settable property of an rvalue value type is non-settable:
9697
fz().settable_x = x // expected-error{{cannot assign to property: 'fz' returns immutable value}}
@@ -111,7 +112,7 @@ z.non_settable_x.property = 1.0 // expected-error{{cannot assign to property: 'n
111112
f2(&z.non_settable_x.property) // expected-error{{cannot pass immutable value as inout argument: 'non_settable_x' is a get-only property}}
112113
f1(&z.non_settable_x.property) // expected-error{{cannot pass immutable value as inout argument: 'non_settable_x' is a get-only property}}
113114
z.non_settable_x.property += 1.0 // expected-error{{left side of mutating operator isn't mutable: 'non_settable_x' is a get-only property}}
114-
+++z.non_settable_x.property // expected-error{{cannot pass immutable value as inout argument: 'non_settable_x' is a get-only property}}
115+
+++z.non_settable_x.property // expected-error{{cannot convert value of type 'Double' to expected argument type 'X'}}
115116

116117
// settable property of a non-settable reference type IS SETTABLE:
117118
z.non_settable_reftype.property = 1.0

test/Constraints/operator.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,8 @@ func rdar37290898(_ arr: inout [P_37290898], _ element: S_37290898?) {
205205
// SR-8221
206206
infix operator ??=
207207
func ??= <T>(lhs: inout T?, rhs: T?) {}
208-
var c: Int = 0
209-
c ??= 5 // expected-error{{binary operator '??=' cannot be applied to two 'Int' operands}}
210-
// expected-note@-1{{expected an argument list of type '(inout T?, T?)'}}
208+
var c: Int = 0 // expected-note {{change variable type to 'Int?' if it doesn't need to be declared as 'Int'}}
209+
c ??= 5 // expected-error{{inout argument could be set to a value with a type other than 'Int'; use a value declared as type 'Int?' instead}}
211210

212211
func rdar46459603() {
213212
enum E {

0 commit comments

Comments
 (0)