Skip to content

Commit 0b50648

Browse files
committed
[sema] Add a fixit for label-mismatch tuple patterns in for-each statement. rdar://25671800
In the following code example, compiler emits an error of "cannot express tuple conversion...". However, this is trivially fixable by adding multiple labels in the tuple pattern of the for-each statement. This commit adds such fixit. func foo(array : [(some: Int, (key: Int, value: String))]) { for (i, (k, v)) in array { } }
1 parent 4d32407 commit 0b50648

File tree

6 files changed

+110
-21
lines changed

6 files changed

+110
-21
lines changed

lib/Sema/CSApply.cpp

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -367,11 +367,15 @@ namespace {
367367
///
368368
/// \param variadicArgs The source indices that are mapped to the variadic
369369
/// parameter of the resulting tuple, as provided by \c computeTupleShuffle.
370+
///
371+
/// \param typeFromPattern Optionally, the caller can specifiy the pattern
372+
/// from where the toType is derived, so that we can deliver better fixit.
370373
Expr *coerceTupleToTuple(Expr *expr, TupleType *fromTuple,
371374
TupleType *toTuple,
372375
ConstraintLocatorBuilder locator,
373376
SmallVectorImpl<int> &sources,
374-
SmallVectorImpl<unsigned> &variadicArgs);
377+
SmallVectorImpl<unsigned> &variadicArgs,
378+
Optional<Pattern*> typeFromPattern = None);
375379

376380
/// \brief Coerce the given scalar value to the given tuple type.
377381
///
@@ -407,7 +411,8 @@ namespace {
407411
/// \brief Coerce an expression of (possibly unchecked) optional
408412
/// type to have a different (possibly unchecked) optional type.
409413
Expr *coerceOptionalToOptional(Expr *expr, Type toType,
410-
ConstraintLocatorBuilder locator);
414+
ConstraintLocatorBuilder locator,
415+
Optional<Pattern*> typeFromPattern = None);
411416

412417
/// \brief Coerce an expression of implicitly unwrapped optional type to its
413418
/// underlying value type, in the correct way for an implicit
@@ -1083,10 +1088,13 @@ namespace {
10831088
/// \param expr The expression to coerce.
10841089
/// \param toType The type to coerce the expression to.
10851090
/// \param locator Locator used to describe where in this expression we are.
1091+
/// \param typeFromPattern Optionally, the caller can specifiy the pattern
1092+
/// from where the toType is derived, so that we can deliver better fixit.
10861093
///
10871094
/// \returns the coerced expression, which will have type \c ToType.
10881095
Expr *coerceToType(Expr *expr, Type toType,
1089-
ConstraintLocatorBuilder locator);
1096+
ConstraintLocatorBuilder locator,
1097+
Optional<Pattern*> typeFromPattern = None);
10901098

10911099
/// \brief Coerce the given expression (which is the argument to a call) to
10921100
/// the given parameter type.
@@ -3673,6 +3681,56 @@ findDefaultArgsOwner(ConstraintSystem &cs, const Solution &solution,
36733681
return nullptr;
36743682
}
36753683

3684+
static bool
3685+
shouldApplyAddingLabelFixit(TuplePattern *tuplePattern, TupleType *fromTuple,
3686+
TupleType *toTuple,
3687+
std::vector<std::pair<SourceLoc, std::string>> &locInsertPairs) {
3688+
std::vector<TuplePattern*> patternParts;
3689+
std::vector<TupleType*> fromParts;
3690+
std::vector<TupleType*> toParts;
3691+
patternParts.push_back(tuplePattern);
3692+
fromParts.push_back(fromTuple);
3693+
toParts.push_back(toTuple);
3694+
while(!patternParts.empty()) {
3695+
TuplePattern *curPattern = patternParts.back();
3696+
TupleType *curFrom = fromParts.back();
3697+
TupleType *curTo = toParts.back();
3698+
patternParts.pop_back();
3699+
fromParts.pop_back();
3700+
toParts.pop_back();
3701+
unsigned n = curPattern->getElements().size();
3702+
if (curFrom->getElements().size() != n ||
3703+
curTo->getElements().size() != n)
3704+
return false;
3705+
for (unsigned i = 0; i < n; i ++) {
3706+
Pattern* subPat = curPattern->getElement(i).getPattern();
3707+
const TupleTypeElt &subFrom = curFrom->getElement(i);
3708+
const TupleTypeElt &subTo = curTo->getElement(i);
3709+
if ((subFrom.getType()->getKind() == TypeKind::Tuple) ^
3710+
(subTo.getType()->getKind() == TypeKind::Tuple))
3711+
return false;
3712+
auto addLabelFunc = [&]() {
3713+
if (subFrom.getName().empty() && !subTo.getName().empty()) {
3714+
llvm::SmallString<8> Name;
3715+
Name.append(subTo.getName().str());
3716+
Name.append(": ");
3717+
locInsertPairs.push_back({subPat->getStartLoc(), Name.str()});
3718+
}
3719+
};
3720+
if (auto subFromTuple = subFrom.getType()->getAs<TupleType>()) {
3721+
fromParts.push_back(subFromTuple);
3722+
toParts.push_back(subTo.getType()->getAs<TupleType>());
3723+
patternParts.push_back(static_cast<TuplePattern*>(subPat));
3724+
addLabelFunc();
3725+
} else if (subFrom.getType()->isEqual(subTo.getType())) {
3726+
addLabelFunc();
3727+
} else
3728+
return false;
3729+
}
3730+
}
3731+
return true;
3732+
}
3733+
36763734
/// Produce the caller-side default argument for this default argument, or
36773735
/// null if the default argument will be provided by the callee.
36783736
static std::pair<Expr *, DefaultArgumentKind>
@@ -3792,7 +3850,8 @@ Expr *ExprRewriter::coerceTupleToTuple(Expr *expr, TupleType *fromTuple,
37923850
TupleType *toTuple,
37933851
ConstraintLocatorBuilder locator,
37943852
SmallVectorImpl<int> &sources,
3795-
SmallVectorImpl<unsigned> &variadicArgs){
3853+
SmallVectorImpl<unsigned> &variadicArgs,
3854+
Optional<Pattern*> typeFromPattern){
37963855
auto &tc = cs.getTypeChecker();
37973856

37983857
// Capture the tuple expression, if there is one.
@@ -3883,9 +3942,19 @@ Expr *ExprRewriter::coerceTupleToTuple(Expr *expr, TupleType *fromTuple,
38833942
// We need to convert the source element to the destination type.
38843943
if (!fromTupleExpr) {
38853944
// FIXME: Lame! We can't express this in the AST.
3886-
tc.diagnose(expr->getLoc(),
3887-
diag::tuple_conversion_not_expressible,
3888-
fromTuple, toTuple);
3945+
InFlightDiagnostic diag = tc.diagnose(expr->getLoc(),
3946+
diag::tuple_conversion_not_expressible,
3947+
fromTuple, toTuple);
3948+
if (typeFromPattern) {
3949+
std::vector<std::pair<SourceLoc, std::string>> locInsertPairs;
3950+
TuplePattern *tupleP = dyn_cast<TuplePattern>(typeFromPattern.getValue());
3951+
if (tupleP && shouldApplyAddingLabelFixit(tupleP, toTuple, fromTuple,
3952+
locInsertPairs)) {
3953+
for (auto &Pair : locInsertPairs) {
3954+
diag.fixItInsert(Pair.first, Pair.second);
3955+
}
3956+
}
3957+
}
38893958
return nullptr;
38903959
}
38913960

@@ -4238,7 +4307,8 @@ static Type getOptionalBaseType(const Type &type) {
42384307
}
42394308

42404309
Expr *ExprRewriter::coerceOptionalToOptional(Expr *expr, Type toType,
4241-
ConstraintLocatorBuilder locator) {
4310+
ConstraintLocatorBuilder locator,
4311+
Optional<Pattern*> typeFromPattern) {
42424312
auto &tc = cs.getTypeChecker();
42434313
Type fromType = expr->getType();
42444314

@@ -4287,7 +4357,7 @@ Expr *ExprRewriter::coerceOptionalToOptional(Expr *expr, Type toType,
42874357
expr = new (tc.Context) BindOptionalExpr(expr, expr->getSourceRange().End,
42884358
/*depth*/ 0, fromValueType);
42894359
expr->setImplicit(true);
4290-
expr = coerceToType(expr, toValueType, locator);
4360+
expr = coerceToType(expr, toValueType, locator, typeFromPattern);
42914361
if (!expr) return nullptr;
42924362

42934363
expr = new (tc.Context) InjectIntoOptionalExpr(expr, toType);
@@ -4743,7 +4813,8 @@ maybeDiagnoseUnsupportedFunctionConversion(TypeChecker &tc, Expr *expr,
47434813
}
47444814

47454815
Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
4746-
ConstraintLocatorBuilder locator) {
4816+
ConstraintLocatorBuilder locator,
4817+
Optional<Pattern*> typeFromPattern) {
47474818
auto &tc = cs.getTypeChecker();
47484819

47494820
// The type we're converting from.
@@ -4769,7 +4840,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
47694840
assert(!failed && "Couldn't convert tuple to tuple?");
47704841
(void)failed;
47714842
return coerceTupleToTuple(expr, fromTuple, toTuple, locator, sources,
4772-
variadicArgs);
4843+
variadicArgs, typeFromPattern);
47734844
}
47744845

47754846
case ConversionRestrictionKind::ScalarToTuple: {
@@ -4876,7 +4947,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
48764947
case ConversionRestrictionKind::OptionalToImplicitlyUnwrappedOptional:
48774948
case ConversionRestrictionKind::ImplicitlyUnwrappedOptionalToOptional:
48784949
case ConversionRestrictionKind::OptionalToOptional:
4879-
return coerceOptionalToOptional(expr, toType, locator);
4950+
return coerceOptionalToOptional(expr, toType, locator, typeFromPattern);
48804951

48814952
case ConversionRestrictionKind::ForceUnchecked: {
48824953
auto valueTy = fromType->getImplicitlyUnwrappedOptionalObjectType();
@@ -5175,7 +5246,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
51755246

51765247
if (toType->getAnyOptionalObjectType() &&
51775248
expr->getType()->getAnyOptionalObjectType()) {
5178-
return coerceOptionalToOptional(expr, toType, locator);
5249+
return coerceOptionalToOptional(expr, toType, locator, typeFromPattern);
51795250
}
51805251

51815252
// Coercion to Optional<T>.
@@ -6193,12 +6264,13 @@ Expr *ConstraintSystem::applySolutionShallow(const Solution &solution,
61936264

61946265
Expr *Solution::coerceToType(Expr *expr, Type toType,
61956266
ConstraintLocator *locator,
6196-
bool ignoreTopLevelInjection) const {
6267+
bool ignoreTopLevelInjection,
6268+
Optional<Pattern*> typeFromPattern) const {
61976269
auto &cs = getConstraintSystem();
61986270
ExprRewriter rewriter(cs, *this,
61996271
/*suppressDiagnostics=*/false,
62006272
/*skipClosures=*/false);
6201-
Expr *result = rewriter.coerceToType(expr, toType, locator);
6273+
Expr *result = rewriter.coerceToType(expr, toType, locator, typeFromPattern);
62026274
if (!result)
62036275
return nullptr;
62046276

lib/Sema/ConstraintSystem.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,9 +615,14 @@ class Solution {
615615
/// on a suspicious top-level optional injection (because the caller already
616616
/// diagnosed it).
617617
///
618+
/// \param typeFromPattern Optionally, the caller can specifiy the pattern
619+
/// from where the toType is derived, so that we can deliver better fixit.
620+
///
618621
/// \returns the coerced expression, which will have type \c ToType.
619-
Expr *coerceToType(Expr *expr, Type toType, ConstraintLocator *locator,
620-
bool ignoreTopLevelInjection = false) const;
622+
Expr *coerceToType(Expr *expr, Type toType,
623+
ConstraintLocator *locator,
624+
bool ignoreTopLevelInjection = false,
625+
Optional<Pattern*> typeFromPattern = None) const;
621626

622627
/// \brief Convert the given expression to a logic value.
623628
///

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2239,7 +2239,8 @@ Expr *TypeChecker::coerceToMaterializable(Expr *expr) {
22392239
return expr;
22402240
}
22412241

2242-
bool TypeChecker::convertToType(Expr *&expr, Type type, DeclContext *dc) {
2242+
bool TypeChecker::convertToType(Expr *&expr, Type type, DeclContext *dc,
2243+
Optional<Pattern*> typeFromPattern) {
22432244
// TODO: need to add kind arg?
22442245
// Construct a constraint system from this expression.
22452246
ConstraintSystem cs(*this, dc, ConstraintSystemFlags::AllowFixes);
@@ -2274,7 +2275,8 @@ bool TypeChecker::convertToType(Expr *&expr, Type type, DeclContext *dc) {
22742275

22752276
// Perform the conversion.
22762277
Expr *result = solution.coerceToType(expr, type,
2277-
cs.getConstraintLocator(expr));
2278+
cs.getConstraintLocator(expr),
2279+
false, typeFromPattern);
22782280
if (!result) {
22792281
return true;
22802282
}

lib/Sema/TypeCheckStmt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
673673
// Convert that Optional<T> value to Optional<Element>.
674674
auto optPatternType = OptionalType::get(S->getPattern()->getType());
675675
if (!optPatternType->isEqual(iteratorNext->getType()) &&
676-
TC.convertToType(iteratorNext, optPatternType, DC)) {
676+
TC.convertToType(iteratorNext, optPatternType, DC, S->getPattern())) {
677677
return nullptr;
678678
}
679679

lib/Sema/TypeChecker.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,9 +1339,12 @@ class TypeChecker final : public LazyResolver {
13391339
///
13401340
/// \param expr The expression, which will be updated in place.
13411341
/// \param type The type to convert to.
1342+
/// \param typeFromPattern Optionally, the caller can specifiy the pattern
1343+
/// from where the toType is derived, so that we can deliver better fixit.
13421344
///
13431345
/// \returns true if an error occurred, false otherwise.
1344-
bool convertToType(Expr *&expr, Type type, DeclContext *dc);
1346+
bool convertToType(Expr *&expr, Type type, DeclContext *dc,
1347+
Optional<Pattern*> typeFromPattern = None);
13451348

13461349
/// \brief Coerce the given expression to an rvalue, if it isn't already.
13471350
Expr *coerceToRValue(Expr *expr);

test/Sema/diag_express_tuple.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// RUN: %target-swift-frontend -parse -verify %s
2+
3+
func foo(a : [(some: Int, (key: Int, value: String))]) -> String {
4+
for (i , (j, k)) in a { // expected-error {{cannot express tuple conversion '(some: Int, (key: Int, value: String))' to '(Int, (Int, String))'}}{8-8=some: }} {{13-13=key: }} {{16-16=value: }}
5+
if i == j { return k }
6+
}
7+
}

0 commit comments

Comments
 (0)