Skip to content

[ConstraintSystem] Add a type variable merging heuristic to addJoinConstraint #33296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 7, 2020
27 changes: 27 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4942,6 +4942,9 @@ bool CollectionElementContextualFailure::diagnoseAsError() {

Optional<InFlightDiagnostic> diagnostic;
if (isExpr<ArrayExpr>(anchor)) {
if (diagnoseMergedLiteralElements())
return true;

diagnostic.emplace(emitDiagnostic(diag::cannot_convert_array_element,
eltType, contextualType));
}
Expand Down Expand Up @@ -4992,6 +4995,30 @@ bool CollectionElementContextualFailure::diagnoseAsError() {
return true;
}

bool CollectionElementContextualFailure::diagnoseMergedLiteralElements() {
auto elementAnchor = simplifyLocatorToAnchor(getLocator());
if (!elementAnchor)
return false;

auto *typeVar = getRawType(elementAnchor)->getAs<TypeVariableType>();
if (!typeVar || !typeVar->getImpl().getAtomicLiteralKind())
return false;

// This element is a literal whose type variable could have been merged with others,
// but the conversion constraint to the array element type was only placed on one
// of them. So, we want to emit the error for each element whose type variable is in
// this equivalence class.
auto &cs = getConstraintSystem();
auto node = cs.getRepresentative(typeVar)->getImpl().getGraphNode();
for (const auto *typeVar : node->getEquivalenceClass()) {
auto anchor = typeVar->getImpl().getLocator()->getAnchor();
emitDiagnosticAt(constraints::getLoc(anchor), diag::cannot_convert_array_element,
getFromType(), getToType());
}

return true;
}

bool MissingContextualConformanceFailure::diagnoseAsError() {
auto anchor = getAnchor();
auto path = getLocator()->getPath();
Expand Down
2 changes: 2 additions & 0 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -1637,6 +1637,8 @@ class CollectionElementContextualFailure final : public ContextualFailure {
: ContextualFailure(solution, eltType, contextualType, locator) {}

bool diagnoseAsError() override;

bool diagnoseMergedLiteralElements();
};

class MissingContextualConformanceFailure final : public ContextualFailure {
Expand Down
42 changes: 16 additions & 26 deletions lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1803,31 +1803,28 @@ namespace {

auto locator = CS.getConstraintLocator(expr);
auto contextualType = CS.getContextualType(expr);
Type contextualArrayType = nullptr;
Type contextualArrayElementType = nullptr;


auto joinElementTypes = [&](Optional<Type> elementType) {
const auto elements = expr->getElements();
unsigned index = 0;

using Iterator = decltype(elements)::iterator;
CS.addJoinConstraint<Iterator>(locator, elements.begin(), elements.end(),
elementType, [&](const auto it) {
auto *locator = CS.getConstraintLocator(expr, LocatorPathElt::TupleElement(index++));
return std::make_pair(CS.getType(*it), locator);
});
};

// If a contextual type exists for this expression, apply it directly.
Optional<Type> arrayElementType;
if (contextualType &&
(arrayElementType = ConstraintSystem::isArrayType(contextualType))) {
// Is the array type a contextual type
contextualArrayType = contextualType;
contextualArrayElementType = *arrayElementType;

CS.addConstraint(ConstraintKind::LiteralConformsTo, contextualType,
arrayProto->getDeclaredType(),
locator);

unsigned index = 0;
for (auto element : expr->getElements()) {
CS.addConstraint(ConstraintKind::Conversion,
CS.getType(element),
contextualArrayElementType,
CS.getConstraintLocator(
expr, LocatorPathElt::TupleElement(index++)));
}

return contextualArrayType;
joinElementTypes(arrayElementType);
return contextualType;
}

// Produce a specialized diagnostic if this is an attempt to initialize
Expand Down Expand Up @@ -1893,14 +1890,7 @@ namespace {

// Introduce conversions from each element to the element type of the
// array.
unsigned index = 0;
for (auto element : expr->getElements()) {
CS.addConstraint(ConstraintKind::Conversion,
CS.getType(element),
arrayElementTy,
CS.getConstraintLocator(
expr, LocatorPathElt::TupleElement(index++)));
}
joinElementTypes(arrayElementTy);

// The array element type defaults to 'Any'.
CS.addConstraint(ConstraintKind::Defaultable, arrayElementTy,
Expand Down
30 changes: 1 addition & 29 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4214,6 +4214,7 @@ bool ConstraintSystem::repairFailures(

conversionsOrFixes.push_back(CollectionElementContextualMismatch::create(
*this, lhs, rhs, getConstraintLocator(locator)));
break;
}

// Drop the `tuple element` locator element so that all tuple element
Expand Down Expand Up @@ -10418,35 +10419,6 @@ void ConstraintSystem::addContextualConversionConstraint(
convertTypeLocator, /*isFavored*/ true);
}

Type ConstraintSystem::addJoinConstraint(
ConstraintLocator *locator,
ArrayRef<std::pair<Type, ConstraintLocator *>> inputs) {
switch (inputs.size()) {
case 0:
return Type();

case 1:
return inputs.front().first;

default:
// Produce the join below.
break;
}

// Create a type variable to capture the result of the join.
Type resultTy = createTypeVariable(locator,
(TVO_PrefersSubtypeBinding |
TVO_CanBindToNoEscape));

// Introduce conversions from each input type to the type variable.
for (const auto &input : inputs) {
addConstraint(
ConstraintKind::Conversion, input.first, resultTy, input.second);
}

return resultTy;
}

void ConstraintSystem::addFixConstraint(ConstraintFix *fix, ConstraintKind kind,
Type first, Type second,
ConstraintLocatorBuilder locator,
Expand Down
75 changes: 71 additions & 4 deletions lib/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@ class TypeVariableType::Implementation {
/// Retrieve the generic parameter opened by this type variable.
GenericTypeParamType *getGenericParameter() const;

/// Returns the \c ExprKind of this type variable if it's the type of an
/// atomic literal expression, meaning the literal can't be composed of subexpressions.
/// Otherwise, returns \c None.
Optional<ExprKind> getAtomicLiteralKind() const;

/// Determine whether this type variable represents a closure type.
bool isClosureType() const;

Expand Down Expand Up @@ -3076,16 +3081,78 @@ class ConstraintSystem {
Expr *expr, Type conversionType, ContextualTypePurpose purpose,
bool isOpaqueReturnType);

/// Convenience function to pass an \c ArrayRef to \c addJoinConstraint
Type addJoinConstraint(ConstraintLocator *locator,
ArrayRef<std::pair<Type, ConstraintLocator *>> inputs,
Optional<Type> supertype = None) {
return addJoinConstraint<decltype(inputs)::iterator>(
locator, inputs.begin(), inputs.end(), supertype, [](auto it) { return *it; });
}

/// Add a "join" constraint between a set of types, producing the common
/// supertype.
///
/// Currently, a "join" is modeled by a set of conversion constraints to
/// a new type variable. At some point, we may want a new constraint kind
/// to cover the join.
/// a new type variable or a specified supertype. At some point, we may want
/// a new constraint kind to cover the join.
///
/// \returns the joined type, which is generally a new type variable.
/// \note This method will merge any input type variables for atomic literal
/// expressions of the same kind. It assumes that if same-kind literal type
/// variables are joined, there will be no differing constraints on those
/// type variables.
///
/// \returns the joined type, which is generally a new type variable, unless there are
/// fewer than 2 input types or the \c supertype parameter is specified.
template<typename Iterator>
Type addJoinConstraint(ConstraintLocator *locator,
ArrayRef<std::pair<Type, ConstraintLocator *>> inputs);
Iterator begin, Iterator end,
Optional<Type> supertype,
std::function<std::pair<Type, ConstraintLocator *>(Iterator)> getType) {
if (begin == end)
return Type();

// No need to generate a new type variable if there's only one type to join
if ((begin + 1 == end) && !supertype.hasValue())
return getType(begin).first;

// The type to capture the result of the join, which is either the specified supertype,
// or a new type variable.
Type resultTy = supertype.hasValue() ? supertype.getValue() :
createTypeVariable(locator, (TVO_PrefersSubtypeBinding | TVO_CanBindToNoEscape));

using RawExprKind = uint8_t;
llvm::SmallDenseMap<RawExprKind, TypeVariableType *> representativeForKind;

// Join the input types.
while (begin != end) {
Type type;
ConstraintLocator *locator;
std::tie(type, locator) = getType(begin++);

// We can merge the type variables of same-kind atomic literal expressions because they
// will all have the same set of constraints and therefore can never resolve to anything
// different.
if (auto *typeVar = type->getAs<TypeVariableType>()) {
if (auto literalKind = typeVar->getImpl().getAtomicLiteralKind()) {
auto *&originalRep = representativeForKind[RawExprKind(*literalKind)];
auto *currentRep = getRepresentative(typeVar);

if (originalRep) {
if (originalRep != currentRep)
mergeEquivalenceClasses(currentRep, originalRep);
continue;
}

originalRep = currentRep;
}
}

// Introduce conversions from each input type to the supertype.
addConstraint(ConstraintKind::Conversion, type, resultTy, locator);
}

return resultTy;
}

/// Add a constraint to the constraint system with an associated fix.
void addFixConstraint(ConstraintFix *fix, ConstraintKind kind,
Expand Down
18 changes: 18 additions & 0 deletions lib/Sema/TypeCheckConstraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ TypeVariableType::Implementation::getGenericParameter() const {
return locator ? locator->getGenericParameter() : nullptr;
}

Optional<ExprKind>
TypeVariableType::Implementation::getAtomicLiteralKind() const {
if (!locator || !locator->directlyAt<LiteralExpr>())
return None;

auto kind = getAsExpr(locator->getAnchor())->getKind();
switch (kind) {
case ExprKind::IntegerLiteral:
case ExprKind::FloatLiteral:
case ExprKind::StringLiteral:
case ExprKind::BooleanLiteral:
case ExprKind::NilLiteral:
return kind;
default:
return None;
}
}

bool TypeVariableType::Implementation::isClosureType() const {
if (!(locator && locator->getAnchor()))
return false;
Expand Down
3 changes: 0 additions & 3 deletions validation-test/SILOptimizer/large_string_array.swift.gyb
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// RUN: %empty-directory(%t)
// RUN: %gyb %s > %t/main.swift

// FIXME: <rdar://problem/66219627> Re-enable SILOptimizer/large_string_array.swift.gyb
// REQUIRES: rdar66219627

// The compiler should finish in less than 1 minute. To give some slack,
// specify a timeout of 5 minutes.
// If the compiler needs more than 5 minutes, there is probably a real problem.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s --expected-exit-code 1
// RUN: %scale-test --begin 1 --end 5 --step 1 --select NumLeafScopes %s --expected-exit-code 1
// REQUIRES: asserts,no_asan

% enum_cases = 3
Expand Down