Skip to content

Commit

Permalink
[sema] Add a fixit for label-mismatch tuple patterns in for-each stat…
Browse files Browse the repository at this point in the history
…ement. 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 {
  }
}
  • Loading branch information
nkcsgexi committed Apr 12, 2016
1 parent 4d32407 commit 0b50648
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 21 deletions.
102 changes: 87 additions & 15 deletions lib/Sema/CSApply.cpp
Expand Up @@ -367,11 +367,15 @@ namespace {
///
/// \param variadicArgs The source indices that are mapped to the variadic
/// parameter of the resulting tuple, as provided by \c computeTupleShuffle.
///
/// \param typeFromPattern Optionally, the caller can specifiy the pattern
/// from where the toType is derived, so that we can deliver better fixit.
Expr *coerceTupleToTuple(Expr *expr, TupleType *fromTuple,
TupleType *toTuple,
ConstraintLocatorBuilder locator,
SmallVectorImpl<int> &sources,
SmallVectorImpl<unsigned> &variadicArgs);
SmallVectorImpl<unsigned> &variadicArgs,
Optional<Pattern*> typeFromPattern = None);

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

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

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

static bool
shouldApplyAddingLabelFixit(TuplePattern *tuplePattern, TupleType *fromTuple,
TupleType *toTuple,
std::vector<std::pair<SourceLoc, std::string>> &locInsertPairs) {
std::vector<TuplePattern*> patternParts;
std::vector<TupleType*> fromParts;
std::vector<TupleType*> toParts;
patternParts.push_back(tuplePattern);
fromParts.push_back(fromTuple);
toParts.push_back(toTuple);
while(!patternParts.empty()) {
TuplePattern *curPattern = patternParts.back();
TupleType *curFrom = fromParts.back();
TupleType *curTo = toParts.back();
patternParts.pop_back();
fromParts.pop_back();
toParts.pop_back();
unsigned n = curPattern->getElements().size();
if (curFrom->getElements().size() != n ||
curTo->getElements().size() != n)
return false;
for (unsigned i = 0; i < n; i ++) {
Pattern* subPat = curPattern->getElement(i).getPattern();
const TupleTypeElt &subFrom = curFrom->getElement(i);
const TupleTypeElt &subTo = curTo->getElement(i);
if ((subFrom.getType()->getKind() == TypeKind::Tuple) ^
(subTo.getType()->getKind() == TypeKind::Tuple))
return false;
auto addLabelFunc = [&]() {
if (subFrom.getName().empty() && !subTo.getName().empty()) {
llvm::SmallString<8> Name;
Name.append(subTo.getName().str());
Name.append(": ");
locInsertPairs.push_back({subPat->getStartLoc(), Name.str()});
}
};
if (auto subFromTuple = subFrom.getType()->getAs<TupleType>()) {
fromParts.push_back(subFromTuple);
toParts.push_back(subTo.getType()->getAs<TupleType>());
patternParts.push_back(static_cast<TuplePattern*>(subPat));
addLabelFunc();
} else if (subFrom.getType()->isEqual(subTo.getType())) {
addLabelFunc();
} else
return false;
}
}
return true;
}

/// Produce the caller-side default argument for this default argument, or
/// null if the default argument will be provided by the callee.
static std::pair<Expr *, DefaultArgumentKind>
Expand Down Expand Up @@ -3792,7 +3850,8 @@ Expr *ExprRewriter::coerceTupleToTuple(Expr *expr, TupleType *fromTuple,
TupleType *toTuple,
ConstraintLocatorBuilder locator,
SmallVectorImpl<int> &sources,
SmallVectorImpl<unsigned> &variadicArgs){
SmallVectorImpl<unsigned> &variadicArgs,
Optional<Pattern*> typeFromPattern){
auto &tc = cs.getTypeChecker();

// Capture the tuple expression, if there is one.
Expand Down Expand Up @@ -3883,9 +3942,19 @@ Expr *ExprRewriter::coerceTupleToTuple(Expr *expr, TupleType *fromTuple,
// We need to convert the source element to the destination type.
if (!fromTupleExpr) {
// FIXME: Lame! We can't express this in the AST.
tc.diagnose(expr->getLoc(),
diag::tuple_conversion_not_expressible,
fromTuple, toTuple);
InFlightDiagnostic diag = tc.diagnose(expr->getLoc(),
diag::tuple_conversion_not_expressible,
fromTuple, toTuple);
if (typeFromPattern) {
std::vector<std::pair<SourceLoc, std::string>> locInsertPairs;
TuplePattern *tupleP = dyn_cast<TuplePattern>(typeFromPattern.getValue());
if (tupleP && shouldApplyAddingLabelFixit(tupleP, toTuple, fromTuple,
locInsertPairs)) {
for (auto &Pair : locInsertPairs) {
diag.fixItInsert(Pair.first, Pair.second);
}
}
}
return nullptr;
}

Expand Down Expand Up @@ -4238,7 +4307,8 @@ static Type getOptionalBaseType(const Type &type) {
}

Expr *ExprRewriter::coerceOptionalToOptional(Expr *expr, Type toType,
ConstraintLocatorBuilder locator) {
ConstraintLocatorBuilder locator,
Optional<Pattern*> typeFromPattern) {
auto &tc = cs.getTypeChecker();
Type fromType = expr->getType();

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

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

Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
ConstraintLocatorBuilder locator) {
ConstraintLocatorBuilder locator,
Optional<Pattern*> typeFromPattern) {
auto &tc = cs.getTypeChecker();

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

case ConversionRestrictionKind::ScalarToTuple: {
Expand Down Expand Up @@ -4876,7 +4947,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
case ConversionRestrictionKind::OptionalToImplicitlyUnwrappedOptional:
case ConversionRestrictionKind::ImplicitlyUnwrappedOptionalToOptional:
case ConversionRestrictionKind::OptionalToOptional:
return coerceOptionalToOptional(expr, toType, locator);
return coerceOptionalToOptional(expr, toType, locator, typeFromPattern);

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

if (toType->getAnyOptionalObjectType() &&
expr->getType()->getAnyOptionalObjectType()) {
return coerceOptionalToOptional(expr, toType, locator);
return coerceOptionalToOptional(expr, toType, locator, typeFromPattern);
}

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

Expr *Solution::coerceToType(Expr *expr, Type toType,
ConstraintLocator *locator,
bool ignoreTopLevelInjection) const {
bool ignoreTopLevelInjection,
Optional<Pattern*> typeFromPattern) const {
auto &cs = getConstraintSystem();
ExprRewriter rewriter(cs, *this,
/*suppressDiagnostics=*/false,
/*skipClosures=*/false);
Expr *result = rewriter.coerceToType(expr, toType, locator);
Expr *result = rewriter.coerceToType(expr, toType, locator, typeFromPattern);
if (!result)
return nullptr;

Expand Down
9 changes: 7 additions & 2 deletions lib/Sema/ConstraintSystem.h
Expand Up @@ -615,9 +615,14 @@ class Solution {
/// on a suspicious top-level optional injection (because the caller already
/// diagnosed it).
///
/// \param typeFromPattern Optionally, the caller can specifiy the pattern
/// from where the toType is derived, so that we can deliver better fixit.
///
/// \returns the coerced expression, which will have type \c ToType.
Expr *coerceToType(Expr *expr, Type toType, ConstraintLocator *locator,
bool ignoreTopLevelInjection = false) const;
Expr *coerceToType(Expr *expr, Type toType,
ConstraintLocator *locator,
bool ignoreTopLevelInjection = false,
Optional<Pattern*> typeFromPattern = None) const;

/// \brief Convert the given expression to a logic value.
///
Expand Down
6 changes: 4 additions & 2 deletions lib/Sema/TypeCheckConstraints.cpp
Expand Up @@ -2239,7 +2239,8 @@ Expr *TypeChecker::coerceToMaterializable(Expr *expr) {
return expr;
}

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

// Perform the conversion.
Expr *result = solution.coerceToType(expr, type,
cs.getConstraintLocator(expr));
cs.getConstraintLocator(expr),
false, typeFromPattern);
if (!result) {
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckStmt.cpp
Expand Up @@ -673,7 +673,7 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
// Convert that Optional<T> value to Optional<Element>.
auto optPatternType = OptionalType::get(S->getPattern()->getType());
if (!optPatternType->isEqual(iteratorNext->getType()) &&
TC.convertToType(iteratorNext, optPatternType, DC)) {
TC.convertToType(iteratorNext, optPatternType, DC, S->getPattern())) {
return nullptr;
}

Expand Down
5 changes: 4 additions & 1 deletion lib/Sema/TypeChecker.h
Expand Up @@ -1339,9 +1339,12 @@ class TypeChecker final : public LazyResolver {
///
/// \param expr The expression, which will be updated in place.
/// \param type The type to convert to.
/// \param typeFromPattern Optionally, the caller can specifiy the pattern
/// from where the toType is derived, so that we can deliver better fixit.
///
/// \returns true if an error occurred, false otherwise.
bool convertToType(Expr *&expr, Type type, DeclContext *dc);
bool convertToType(Expr *&expr, Type type, DeclContext *dc,
Optional<Pattern*> typeFromPattern = None);

/// \brief Coerce the given expression to an rvalue, if it isn't already.
Expr *coerceToRValue(Expr *expr);
Expand Down
7 changes: 7 additions & 0 deletions test/Sema/diag_express_tuple.swift
@@ -0,0 +1,7 @@
// RUN: %target-swift-frontend -parse -verify %s

func foo(a : [(some: Int, (key: Int, value: String))]) -> String {
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: }}
if i == j { return k }
}
}

2 comments on commit 0b50648

@lattner
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Xi,

This is adding a bunch of complexity to the interface of coerceTupleToTuple. Would it be possible to derive this from the locator it already has?

@nkcsgexi
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Chris,
Jordan and I discussed this before implementing the fixit and we considered multiple options to pass down the tuple pattern. Using locator seems to be hard here because our locators tightly bind to exprs, which is possibly a design glitch at the first place. Passing down the patterns, which seems ugly for sure, seems the only feasible way to make this happen.

Please sign in to comment.