From 49b833b51aa6522ec1bd2d46f98cb8c06ea3ba89 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 11 Oct 2016 16:57:53 -0700 Subject: [PATCH] [Type checker] Eliminate the 'literalConformanceProto' state on type variables. The 'literalConformanceProto' field of TypeVariableType::Implementation didn't take into account equivalence classes of type variables. Eliminate it, and either look at the actual expressions (for optimizing constraints during constraint generation) or the actual constraints on a given type variable (for determining whether to include optionals in the set of potential type variable bindings). (cherry picked from commit 6bdd9cfae5bbba8960d55d9695b6563a24329bab) --- lib/Sema/CSDiag.cpp | 3 +- lib/Sema/CSGen.cpp | 108 +++++++++++++++++------------------- lib/Sema/CSSimplify.cpp | 3 + lib/Sema/CSSolver.cpp | 46 +++++++++++---- lib/Sema/Constraint.cpp | 5 ++ lib/Sema/Constraint.h | 4 ++ lib/Sema/ConstraintSystem.h | 6 -- 7 files changed, 101 insertions(+), 74 deletions(-) diff --git a/lib/Sema/CSDiag.cpp b/lib/Sema/CSDiag.cpp index 2d73b800c16..d9cb81d8149 100644 --- a/lib/Sema/CSDiag.cpp +++ b/lib/Sema/CSDiag.cpp @@ -2920,7 +2920,8 @@ bool FailureDiagnosis::diagnoseGeneralConversionFailure(Constraint *constraint){ // If simplification has turned this into the same types, then this isn't the // broken constraint that we're looking for. if (fromType->isEqual(toType) && - constraint->getKind() != ConstraintKind::ConformsTo) + constraint->getKind() != ConstraintKind::ConformsTo && + constraint->getKind() != ConstraintKind::LiteralConformsTo) return false; diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 8dcea53c726..e597967f131 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -500,49 +500,47 @@ namespace { return false; } - /// Determine whether the given parameter and argument type should be + /// Determine whether the given parameter type and argument should be /// "favored" because they match exactly. bool isFavoredParamAndArg(ConstraintSystem &CS, Type paramTy, + Expr *arg, Type argTy, - Type otherArgTy) { - if (argTy->getAs()) - argTy = argTy->getLValueOrInOutObjectType(); - - if (!otherArgTy.isNull() && - otherArgTy->getAs()) - otherArgTy = otherArgTy->getLValueOrInOutObjectType(); - + Expr *otherArg = nullptr, + Type otherArgTy = Type()) { + // Determine the argument type. + argTy = argTy->getLValueOrInOutObjectType(); + // Do the types match exactly? if (paramTy->isEqual(argTy)) return true; - - // If the argument is a type variable created for a literal that has a - // default type, this is a favored param/arg pair if the parameter is of - // that default type. - // Is the argument a type variable... - if (auto argTypeVar = argTy->getAs()) { - if (auto proto = argTypeVar->getImpl().literalConformanceProto) { - // If it's a struct type associated with the literal conformance, - // test against it directly. This helps to avoid 'widening' the - // favored type to the default type for the literal. - if (!otherArgTy.isNull() && - otherArgTy->getAs()) { - - if (CS.TC.conformsToProtocol(otherArgTy, - proto, - CS.DC, - ConformanceCheckFlags::InExpression)) { - return otherArgTy->isEqual(paramTy); - } - } else if (auto defaultTy = CS.TC.getDefaultType(proto, CS.DC)) { - if (paramTy->isEqual(defaultTy)) { - return true; - } - } - } + + // If the argument is a literal, this is a favored param/arg pair if + // the parameter is of that default type. + auto &tc = CS.getTypeChecker(); + auto literalProto = tc.getLiteralProtocol(arg->getSemanticsProvidingExpr()); + if (!literalProto) return false; + + // Dig out the second argument type. + if (otherArgTy) + otherArgTy = otherArgTy->getLValueOrInOutObjectType(); + + // If there is another, concrete argument, check whether it's type + // conforms to the literal protocol and test against it directly. + // This helps to avoid 'widening' the favored type to the default type for + // the literal. + if (otherArgTy && otherArgTy->getAnyNominal()) { + return otherArgTy->isEqual(paramTy) && + tc.conformsToProtocol(otherArgTy, literalProto, CS.DC, + ConformanceCheckFlags::InExpression); } - + + // If there is a default type for the literal protocol, check whether + // it is the same as the parameter type. + // Check whether there is a default type to compare against. + if (Type defaultType = tc.getDefaultType(literalProto, CS.DC)) + return paramTy->isEqual(defaultType); + return false; } @@ -742,9 +740,6 @@ namespace { /// for the operand and contextual type. void favorMatchingUnaryOperators(ApplyExpr *expr, ConstraintSystem &CS) { - // Find the argument type. - auto argTy = expr->getArg()->getType()->getWithoutParens(); - // Determine whether the given declaration is favored. auto isFavoredDecl = [&](ValueDecl *value) -> bool { auto valueTy = value->getType(); @@ -762,7 +757,8 @@ namespace { auto resultTy = fnTy->getResult(); auto contextualTy = CS.getContextualType(expr); - return isFavoredParamAndArg(CS, paramTy, argTy, Type()) && + return isFavoredParamAndArg(CS, paramTy, expr->getArg(), + expr->getArg()->getType()->getWithoutParens()) && (!contextualTy || contextualTy->isEqual(resultTy)); }; @@ -881,8 +877,10 @@ namespace { if (!fnTy) return false; - auto firstFavoredTy = CS.getFavoredType(argTupleExpr->getElement(0)); - auto secondFavoredTy = CS.getFavoredType(argTupleExpr->getElement(1)); + Expr *firstArg = argTupleExpr->getElement(0); + auto firstFavoredTy = CS.getFavoredType(firstArg); + Expr *secondArg = argTupleExpr->getElement(1); + auto secondFavoredTy = CS.getFavoredType(secondArg); auto favoredExprTy = CS.getFavoredType(expr); @@ -926,8 +924,10 @@ namespace { auto contextualTy = CS.getContextualType(expr); return - (isFavoredParamAndArg(CS, firstParamTy, firstArgTy, secondArgTy) || - isFavoredParamAndArg(CS, secondParamTy, secondArgTy, firstArgTy)) && + (isFavoredParamAndArg(CS, firstParamTy, firstArg, firstArgTy, + secondArg, secondArgTy) || + isFavoredParamAndArg(CS, secondParamTy, secondArg, secondArgTy, + firstArg, firstArgTy)) && firstParamTy->isEqual(secondParamTy) && (!contextualTy || contextualTy->isEqual(resultTy)); }; @@ -1083,7 +1083,7 @@ namespace { auto keyTy = dictTy->first; auto valueTy = dictTy->second; - if (isFavoredParamAndArg(CS, keyTy, index->getType(), Type())) { + if (isFavoredParamAndArg(CS, keyTy, index, index->getType())) { outputTy = OptionalType::get(valueTy); if (isLValueBase) @@ -1164,10 +1164,7 @@ namespace { auto tv = CS.createTypeVariable(CS.getConstraintLocator(expr), TVO_PrefersSubtypeBinding); - - tv->getImpl().literalConformanceProto = protocol; - - CS.addConstraint(ConstraintKind::ConformsTo, tv, + CS.addConstraint(ConstraintKind::LiteralConformsTo, tv, protocol->getDeclaredType(), CS.getConstraintLocator(expr)); return tv; @@ -1190,8 +1187,7 @@ namespace { // ExpressibleByStringInterpolation protocol. auto locator = CS.getConstraintLocator(expr); auto tv = CS.createTypeVariable(locator, TVO_PrefersSubtypeBinding); - tv->getImpl().literalConformanceProto = interpolationProto; - CS.addConstraint(ConstraintKind::ConformsTo, tv, + CS.addConstraint(ConstraintKind::LiteralConformsTo, tv, interpolationProto->getDeclaredType(), locator); @@ -1264,9 +1260,7 @@ namespace { auto tv = CS.createTypeVariable(CS.getConstraintLocator(expr), TVO_PrefersSubtypeBinding); - tv->getImpl().literalConformanceProto = protocol; - - CS.addConstraint(ConstraintKind::ConformsTo, tv, + CS.addConstraint(ConstraintKind::LiteralConformsTo, tv, protocol->getDeclaredType(), CS.getConstraintLocator(expr)); @@ -1683,7 +1677,7 @@ namespace { contextualArrayElementType = CS.getBaseTypeForArrayType(contextualType.getPointer()); - CS.addConstraint(ConstraintKind::ConformsTo, contextualType, + CS.addConstraint(ConstraintKind::LiteralConformsTo, contextualType, arrayProto->getDeclaredType(), locator); @@ -1703,7 +1697,7 @@ namespace { auto arrayTy = CS.createTypeVariable(locator, TVO_PrefersSubtypeBinding); // The array must be an array literal type. - CS.addConstraint(ConstraintKind::ConformsTo, arrayTy, + CS.addConstraint(ConstraintKind::LiteralConformsTo, arrayTy, arrayProto->getDeclaredType(), locator); @@ -1769,8 +1763,8 @@ namespace { auto dictionaryTy = CS.createTypeVariable(locator, TVO_PrefersSubtypeBinding); - // The array must be a dictionary literal type. - CS.addConstraint(ConstraintKind::ConformsTo, dictionaryTy, + // The dictionary must be a dictionary literal type. + CS.addConstraint(ConstraintKind::LiteralConformsTo, dictionaryTy, dictionaryProto->getDeclaredType(), locator); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 4fd53c24621..c2d5d3f03a0 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2288,6 +2288,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint( return SolutionKind::Solved; break; case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: // Check whether this type conforms to the protocol. if (TC.conformsToProtocol(type, protocol, DC, ConformanceCheckFlags::InExpression)) @@ -3467,6 +3468,7 @@ static TypeMatchKind getTypeMatchKind(ConstraintKind kind) { llvm_unreachable("Overload binding constraints don't involve type matches"); case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: case ConstraintKind::SelfObjectOfProtocol: llvm_unreachable("Conformance constraints don't involve type matches"); @@ -4126,6 +4128,7 @@ ConstraintSystem::simplifyConstraint(const Constraint &constraint) { return SolutionKind::Solved; case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: case ConstraintKind::SelfObjectOfProtocol: return simplifyConformsToConstraint( constraint.getFirstType(), diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp index 04961c7a687..3772c9c94f9 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -62,14 +62,24 @@ static Optional checkTypeOfBinding(ConstraintSystem &cs, return None; // If the type is a type variable itself, don't permit the binding. - // FIXME: This is a hack. We need to be smarter about whether there's enough - // structure in the type to produce an interesting binding, or not. if (auto bindingTypeVar = type->getRValueType()->getAs()) { - if (isNilLiteral && - bindingTypeVar->getImpl().literalConformanceProto && - bindingTypeVar->getImpl().literalConformanceProto->isSpecificProtocol( - KnownProtocolKind::ExpressibleByNilLiteral)) - *isNilLiteral = true; + if (isNilLiteral) { + *isNilLiteral = false; + + // Look for a literal-conformance constraint on the type variable. + SmallVector constraints; + cs.getConstraintGraph().gatherConstraints(bindingTypeVar, constraints); + for (auto constraint : constraints) { + if (constraint->getKind() == ConstraintKind::LiteralConformsTo && + constraint->getProtocol()->isSpecificProtocol( + KnownProtocolKind::ExpressibleByNilLiteral) && + cs.simplifyType(constraint->getFirstType()) + ->isEqual(bindingTypeVar)) { + *isNilLiteral = true; + break; + } + } + } return None; } @@ -667,6 +677,7 @@ static bool shouldBindToValueType(Constraint *constraint) case ConstraintKind::Equal: case ConstraintKind::BindParam: case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: case ConstraintKind::CheckedCast: case ConstraintKind::SelfObjectOfProtocol: case ConstraintKind::ApplicableFunction: @@ -782,8 +793,19 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs, result.InvolvesTypeVariables = true; continue; - case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: + // If there is a 'nil' literal constraint, we might need optional + // supertype bindings. + if (constraint->getProtocol()->isSpecificProtocol( + KnownProtocolKind::ExpressibleByNilLiteral)) + addOptionalSupertypeBindings = true; + + SWIFT_FALLTHROUGH; + + case ConstraintKind::ConformsTo: case ConstraintKind::SelfObjectOfProtocol: { + // FIXME: Only for LiteralConformsTo? + // If there is a default literal type for this protocol, it's a // potential binding. auto defaultType = tc.getDefaultType(constraint->getProtocol(), cs.DC); @@ -908,13 +930,17 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs, // Check whether we can perform this binding. // FIXME: this has a super-inefficient extraneous simplifyType() in it. bool isNilLiteral = false; - if (auto boundType = checkTypeOfBinding(cs, typeVar, type, &isNilLiteral)) { + bool *isNilLiteralPtr = nullptr; + if (!addOptionalSupertypeBindings && kind == AllowedBindingKind::Supertypes) + isNilLiteralPtr = &isNilLiteral; + if (auto boundType = checkTypeOfBinding(cs, typeVar, type, + isNilLiteralPtr)) { type = *boundType; if (type->hasTypeVariable()) result.InvolvesTypeVariables = true; } else { // If the bound is a 'nil' literal type, add optional supertype bindings. - if (isNilLiteral && kind == AllowedBindingKind::Supertypes) { + if (isNilLiteral) { addOptionalSupertypeBindings = true; continue; } diff --git a/lib/Sema/Constraint.cpp b/lib/Sema/Constraint.cpp index 59b5e04d97c..2fb0f6a2160 100644 --- a/lib/Sema/Constraint.cpp +++ b/lib/Sema/Constraint.cpp @@ -61,6 +61,7 @@ Constraint::Constraint(ConstraintKind Kind, Type First, Type Second, case ConstraintKind::OperatorArgumentTupleConversion: case ConstraintKind::OperatorArgumentConversion: case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: case ConstraintKind::CheckedCast: case ConstraintKind::SelfObjectOfProtocol: case ConstraintKind::DynamicTypeOf: @@ -144,6 +145,7 @@ Constraint::Constraint(ConstraintKind kind, Fix fix, ProtocolDecl *Constraint::getProtocol() const { assert((Kind == ConstraintKind::ConformsTo || + Kind == ConstraintKind::LiteralConformsTo || Kind == ConstraintKind::SelfObjectOfProtocol) && "Not a conformance constraint"); return Types.Second->castTo()->getDecl(); @@ -162,6 +164,7 @@ Constraint *Constraint::clone(ConstraintSystem &cs) const { case ConstraintKind::OperatorArgumentTupleConversion: case ConstraintKind::OperatorArgumentConversion: case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: case ConstraintKind::CheckedCast: case ConstraintKind::DynamicTypeOf: case ConstraintKind::SelfObjectOfProtocol: @@ -238,6 +241,7 @@ void Constraint::print(llvm::raw_ostream &Out, SourceManager *sm) const { case ConstraintKind::OperatorArgumentConversion: Out << " operator arg conv "; break; case ConstraintKind::ConformsTo: Out << " conforms to "; break; + case ConstraintKind::LiteralConformsTo: Out << " literal conforms to "; break; case ConstraintKind::CheckedCast: Out << " checked cast to "; break; case ConstraintKind::SelfObjectOfProtocol: Out << " Self type of "; break; case ConstraintKind::ApplicableFunction: Out << " applicable fn "; break; @@ -486,6 +490,7 @@ gatherReferencedTypeVars(Constraint *constraint, case ConstraintKind::BindOverload: case ConstraintKind::Class: case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: case ConstraintKind::SelfObjectOfProtocol: constraint->getFirstType()->getTypeVariables(typeVars); diff --git a/lib/Sema/Constraint.h b/lib/Sema/Constraint.h index 64ebe93a101..22678e23398 100644 --- a/lib/Sema/Constraint.h +++ b/lib/Sema/Constraint.h @@ -81,6 +81,9 @@ enum class ConstraintKind : char { /// \brief The first type must conform to the second type (which is a /// protocol type). ConformsTo, + /// \brief The first type describes a literal that conforms to the second + /// type, which is one of the known expressible-by-literal protocols. + LiteralConformsTo, /// A checked cast from the first type to the second. CheckedCast, /// \brief The first type can act as the Self type of the second type (which @@ -473,6 +476,7 @@ public: case ConstraintKind::OperatorArgumentTupleConversion: case ConstraintKind::OperatorArgumentConversion: case ConstraintKind::ConformsTo: + case ConstraintKind::LiteralConformsTo: case ConstraintKind::CheckedCast: case ConstraintKind::SelfObjectOfProtocol: case ConstraintKind::ApplicableFunction: diff --git a/lib/Sema/ConstraintSystem.h b/lib/Sema/ConstraintSystem.h index ee628e34b06..a45776bdd27 100644 --- a/lib/Sema/ConstraintSystem.h +++ b/lib/Sema/ConstraintSystem.h @@ -158,12 +158,6 @@ class TypeVariableType::Implementation { friend class constraints::SavedTypeVariableBinding; public: - - /// \brief If this type variable is an opened literal expression, keep track - /// of the associated literal conformance for optimization and diagnostic - /// purposes. - ProtocolDecl *literalConformanceProto = nullptr; - explicit Implementation(constraints::ConstraintLocator *locator, unsigned options) : Options(options), locator(locator),