Skip to content

Commit c04c6c9

Browse files
authored
Merge pull request #6586 from xedin/SR_3520
Generic function taking closure with inout parameter returns erroneous results
2 parents ddf35d9 + 0f716e6 commit c04c6c9

File tree

3 files changed

+135
-17
lines changed

3 files changed

+135
-17
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6330,15 +6330,14 @@ visitRebindSelfInConstructorExpr(RebindSelfInConstructorExpr *E) {
63306330

63316331

63326332
bool FailureDiagnosis::visitClosureExpr(ClosureExpr *CE) {
6333+
auto contextualType = CS->getContextualType();
63336334
Type expectedResultType;
6334-
6335+
63356336
// If we have a contextual type available for this closure, apply it to the
63366337
// ParamDecls in our parameter list. This ensures that any uses of them get
63376338
// appropriate types.
6338-
if (CS->getContextualType() &&
6339-
CS->getContextualType()->is<AnyFunctionType>()) {
6340-
6341-
auto fnType = CS->getContextualType()->castTo<AnyFunctionType>();
6339+
if (contextualType && contextualType->is<AnyFunctionType>()) {
6340+
auto fnType = contextualType->getAs<AnyFunctionType>();
63426341
auto *params = CE->getParameters();
63436342
Type inferredArgType = fnType->getInput();
63446343

@@ -6393,12 +6392,24 @@ bool FailureDiagnosis::visitClosureExpr(ClosureExpr *CE) {
63936392
return true;
63946393
}
63956394

6395+
// Coerce parameter types here only if there are no unresolved
63966396
if (CS->TC.coerceParameterListToType(params, CE, fnType))
63976397
return true;
63986398

6399+
for (auto param : *params) {
6400+
auto paramType = param->getType();
6401+
// If this is unresolved 'inout' parameter, it's better to drop
6402+
// 'inout' from type but keep 'mutability' classifier because that
6403+
// might help to diagnose actual problem e.g. type inference and
6404+
// doesn't give us much information anyway.
6405+
if (paramType->is<InOutType>() && paramType->hasUnresolvedType()) {
6406+
param->setType(CS->getASTContext().TheUnresolvedType);
6407+
param->setInterfaceType(param->getType());
6408+
}
6409+
}
6410+
63996411
expectedResultType = fnType->getResult();
64006412
} else {
6401-
64026413
// Defend against type variables from our constraint system leaking into
64036414
// recursive constraints systems formed when checking the body of the
64046415
// closure. These typevars come into them when the body does name
@@ -6417,36 +6428,67 @@ bool FailureDiagnosis::visitClosureExpr(ClosureExpr *CE) {
64176428
if (!CE->hasSingleExpressionBody())
64186429
return false;
64196430

6420-
// If the closure had an expected result type, use it.
6421-
if (CE->hasExplicitResultType())
6422-
expectedResultType = CE->getExplicitResultTypeLoc().getType();
6423-
64246431
// When we're type checking a single-expression closure, we need to reset the
64256432
// DeclContext to this closure for the recursive type checking. Otherwise,
64266433
// if there is a closure in the subexpression, we can violate invariants.
64276434
{
64286435
llvm::SaveAndRestore<DeclContext*> SavedDC(CS->DC, CE);
6429-
6430-
auto CTP = expectedResultType ? CTP_ClosureResult : CTP_Unused;
64316436

64326437
// Explicitly disallow to produce solutions with unresolved type variables,
64336438
// because there is no auxiliary logic which would handle that and it's
64346439
// better to allow failure diagnosis to run directly on the closure body.
64356440
// Note that presence of contextual type implicitly forbids such solutions,
64366441
// but it's not always reset.
6442+
6443+
if (expectedResultType && !CE->hasExplicitResultType()) {
6444+
ExprCleaner cleaner(CE);
6445+
6446+
auto closure = CE->getSingleExpressionBody();
6447+
ConcreteDeclRef decl = nullptr;
6448+
// Let's try to compute result type without mutating AST and
6449+
// using expected (contextual) result type, that's going to help
6450+
// diagnose situations where contextual type expected one result
6451+
// type but actual closure produces a different one without explicitly
6452+
// declaring it (e.g. by using anonymous parameters).
6453+
auto type = CS->TC.getTypeOfExpressionWithoutApplying(
6454+
closure, CS->DC, decl, FreeTypeVariableBinding::Disallow);
6455+
6456+
Type resultType = type.hasValue() ? *type : Type();
6457+
6458+
// Following situations are possible:
6459+
// * No result type - possible structurable problem in the body;
6460+
// * Function result type - possible use of function without calling it,
6461+
// which is properly diagnosed by actual type-check call.
6462+
if (resultType && !resultType->getRValueType()->is<AnyFunctionType>()) {
6463+
if (!resultType->isEqual(expectedResultType)) {
6464+
diagnose(closure->getEndLoc(), diag::cannot_convert_closure_result,
6465+
resultType, expectedResultType);
6466+
return true;
6467+
}
6468+
}
6469+
}
6470+
6471+
// If the closure had an expected result type, use it.
6472+
if (CE->hasExplicitResultType())
6473+
expectedResultType = CE->getExplicitResultTypeLoc().getType();
6474+
6475+
// If we couldn't diagnose anything related to the contextual result type
6476+
// let's run proper type-check with expected type and try to verify it.
6477+
6478+
auto CTP = expectedResultType ? CTP_ClosureResult : CTP_Unused;
64376479
if (!typeCheckChildIndependently(CE->getSingleExpressionBody(),
64386480
expectedResultType, CTP, TCCOptions(),
64396481
nullptr, false))
64406482
return true;
64416483
}
6442-
6484+
64436485
// If the body of the closure looked ok, then look for a contextual type
64446486
// error. This is necessary because FailureDiagnosis::diagnoseExprFailure
64456487
// doesn't do this for closures.
6446-
if (CS->getContextualType() &&
6447-
!CS->getContextualType()->isEqual(CE->getType())) {
6448-
6449-
auto fnType = CS->getContextualType()->getAs<AnyFunctionType>();
6488+
if (contextualType) {
6489+
auto fnType = contextualType->getAs<AnyFunctionType>();
6490+
if (!fnType || fnType->isEqual(CE->getType()))
6491+
return false;
64506492

64516493
// If the closure had an explicitly written return type incompatible with
64526494
// the contextual type, diagnose that.

lib/Sema/CSSimplify.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,40 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
14481448
SWIFT_FALLTHROUGH;
14491449

14501450
case ConstraintKind::Subtype:
1451+
// Subtype constraints are subject for edge contraction,
1452+
// which is inappropriate in this case, because it's going to
1453+
// erase/lose 'inout' modifier after merging equivalence classes
1454+
// (if inout containts type var, see ConstraintGraph::contractEdges()),
1455+
// since right-hand side type variable must not be materializable
1456+
// it can simply get left-hand side as a fixed binding, otherwise fail.
1457+
if (type1->is<InOutType>() &&
1458+
type1->getInOutObjectType()->isTypeVariableOrMember() && typeVar2) {
1459+
// Left-hand side type is not materializable, so we need to
1460+
// check if it's even appropriate to have such a constraint
1461+
// between these two types, or fail early otherwise if right-hand
1462+
// side must be materializable.
1463+
if (typeVar2->getImpl().mustBeMaterializable())
1464+
return SolutionKind::Error;
1465+
1466+
// Constriants like `inout T0 subtype T1` where (T0 must be
1467+
// materializable) are created when closures are part of the generic
1468+
// function parameters e.g. `func foo<T>(_ t: T, (inout T) -> Void) {}`
1469+
// so when such function gets called e.g.
1470+
// ```
1471+
// var x = 42
1472+
// foo(x) { $0 = 0 }
1473+
// ```
1474+
// it's going to try and map closure parameters type (inout T0), where
1475+
// T0 is opened generic parameter T, to argument type (T1), which can
1476+
// be 'inout' but it's uncertain at this stage, but since closure
1477+
// 'declaration' `{ $0 = 0 }` is wrapped inside of a function call,
1478+
// it has to 'map' parameters to arguments instead of converting them,
1479+
// see `ConstraintSystem::matchFunctionTypes`.
1480+
assignFixedType(typeVar2, type1);
1481+
return SolutionKind::Solved;
1482+
}
1483+
SWIFT_FALLTHROUGH;
1484+
14511485
case ConstraintKind::ArgumentConversion:
14521486
case ConstraintKind::OperatorArgumentTupleConversion:
14531487
case ConstraintKind::OperatorArgumentConversion:

test/Constraints/closures.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,45 @@ let mismatchInClosureResultType : (String) -> ((Int) -> Void) = {
395395
return { }
396396
// expected-error@-1 {{contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored}}
397397
}
398+
399+
// SR-3520: Generic function taking closure with inout parameter can result in a variety of compiler errors or EXC_BAD_ACCESS
400+
func sr3520_1<T>(_ g: (inout T) -> Int) {}
401+
sr3520_1 { $0 = 1 } // expected-error {{cannot convert value of type '()' to closure result type 'Int'}}
402+
403+
func sr3520_2<T>(_ item: T, _ update: (inout T) -> Void) {
404+
var x = item
405+
update(&x)
406+
}
407+
var sr3250_arg = 42
408+
sr3520_2(sr3250_arg) { $0 += 3 } // ok
409+
410+
// This test makes sure that having closure with inout argument doesn't crash with member lookup
411+
struct S_3520 {
412+
var number1: Int
413+
}
414+
func sr3520_set_via_closure<S, T>(_ closure: (inout S, T) -> ()) {}
415+
sr3520_set_via_closure({ $0.number1 = $1 }) // expected-error {{type of expression is ambiguous without more context}}
416+
417+
// SR-1976/SR-3073: Inference of inout
418+
func sr1976<T>(_ closure: (inout T) -> Void) {}
419+
sr1976({ $0 += 2 }) // ok
420+
421+
// SR-3073: UnresolvedDotExpr in single expression closure
422+
423+
struct SR3073Lense<Whole, Part> {
424+
let set: (inout Whole, Part) -> ()
425+
}
426+
struct SR3073 {
427+
var number1: Int
428+
func lenses() {
429+
let _: SR3073Lense<SR3073, Int> = SR3073Lense(
430+
set: { $0.number1 = $1 } // ok
431+
)
432+
}
433+
}
434+
435+
// SR-3479: Segmentation fault and other error for closure with inout parameter
436+
func sr3497_unfold<A, B>(_ a0: A, next: (inout A) -> B) {}
437+
func sr3497() {
438+
let _ = sr3497_unfold((0, 0)) { s in 0 } // ok
439+
}

0 commit comments

Comments
 (0)