Implement Type Sequence Parameter Opening

The heart of this patchset: An inference scheme for variadic generic functions and associated pack expansions.

A traditional variadic function looks like

func foo<T>(_ xs: T...) {}

Along with the corresponding function type

<T>(T [variadic]) -> Void

which the constraint system only has to resolve one two for. Hence it opens <T> as a type variable and uses each argument to the function to try to solve for <T>. This approach cannot work for variadic generics as each argument would try to bind to the same <T> and the constraint system would lose coherency in the one case we need it: when the arguments all have different types.

Instead, notice when we encounter expansion types:

func print<T...>(_ xs: T...)
print("Macs say Hello in", 42, "different languages")

We open this type as

print : ($t0...) -> ($t0...)

Now for the brand new stuff: We need to create and bind a pack to $t0, which will trigger the expansion we need for CSApply to see a coherent view of the world. This means we need to examine the argument list and construct the pack type <$t1, $t2, $t3, ...> - one type variable per argument - and bind it to $t0. There's also the catch that each argument that references the opened type $t0 needs to have the same parallel structure, including its arity. The algorithm is thus:

For input type F<... ($t0...), ..., ($tn..) ...> and an apply site F(a0, ..., an) we walk the type `F` and record an entry in a mapping for each opened variadic generic parameter. Now, for each argument ai in (a0, ..., an), we create a fresh type variable corresponding to the argument ai, and record an additional entry in the parameter pack type elements corresponding to that argument.

Concretely, suppose we have

func print2<T..., U...>(first: T..., second: U...) {}
print2(first: "", 42, (), second: [42])

We open print2 as

print2 : ($t0..., $t1...) -> Void

And construct a mapping

  $t0 => <$t2, $t3, $t4> // eventually <String, Int, Void>
  $t1 => <$t5> // eventually [Int]

We then yield the entries of this map back to the solver, which constructs bind constraints where each => exists in the diagram above. The pack type thus is immediately substituted into the corresponding pack expansion type which produces a fully-expanded pack type of the correct arity that the solver can actually use to make forward progress.

Applying the solution is as simple as pulling out the pack type and coercing arguments element-wise into a pack expression.
This commit is contained in:
Robert Widmann
2021-12-16 01:13:46 -08:00
parent 8ac8633f9d
commit 0471e2c58e
3 changed files with 243 additions and 6 deletions

View File

@@ -1239,6 +1239,85 @@ public:
}
};
namespace {
class OpenTypeSequenceElements {
ConstraintSystem &CS;
int64_t argIdx;
Type patternTy;
// A map that relates a type variable opened for a reference to a type
// sequence to an array of parallel type variables, one for each argument.
llvm::MapVector<TypeVariableType *, SmallVector<TypeVariableType *, 2>> SequenceElementCache;
public:
OpenTypeSequenceElements(ConstraintSystem &CS, PackExpansionType *PET)
: CS(CS), argIdx(-1), patternTy(PET->getPatternType()) {}
public:
Type expandParameter() {
argIdx += 1;
return patternTy.transform(*this);
}
void intoPackTypes(llvm::function_ref<void(TypeVariableType *, Type)> fn) && {
if (argIdx == -1) {
(void)patternTy.transform(*this);
for (auto &entry : SequenceElementCache) {
entry.second.clear();
}
}
for (const auto &entry : SequenceElementCache) {
SmallVector<Type, 8> elements;
llvm::transform(entry.second, std::back_inserter(elements), [](Type t) {
return t;
});
auto packType = PackType::get(CS.getASTContext(), elements);
fn(entry.first, packType);
}
}
Type operator()(Type Ty) {
if (!Ty || !Ty->isTypeVariableOrMember())
return Ty;
auto *TV = Ty->getAs<TypeVariableType>();
if (!TV)
return Ty;
// Leave plain opened type variables alone.
if (!TV->getImpl().getGenericParameter()->isTypeSequence())
return TV;
// Create a clone of the type sequence type variable.
auto *clonedTV = CS.createTypeVariable(TV->getImpl().getLocator(),
TV->getImpl().getRawOptions());
// Is there an entry for this type sequence?
const auto &entries = SequenceElementCache.find(TV);
if (entries == SequenceElementCache.end()) {
// We're just seeing this type sequence fresh, so enter a new type
// variable in its place and cache down the fact we just saw it.
SequenceElementCache[TV] = {clonedTV};
} else if ((int64_t)entries->second.size() <= argIdx) {
// We've seen this type sequence before, but we have a brand new element.
// Expand the cache entry.
SequenceElementCache[TV].push_back(clonedTV);
} else {
// Not only have we seen this type sequence before, we've seen this
// argument index before. Extract the old cache entry, emplace the new
// one, and bind them together.
auto *oldEntry = SequenceElementCache[TV][argIdx];
SequenceElementCache[TV][argIdx] = clonedTV;
CS.addConstraint(ConstraintKind::Bind, oldEntry, clonedTV,
TV->getImpl().getLocator());
}
return clonedTV;
}
};
}
// Match the argument of a call to the parameter.
ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
ConstraintSystem &cs, FunctionType *contextualType,
@@ -1376,14 +1455,47 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
for (unsigned paramIdx = 0, numParams = parameterBindings.size();
paramIdx != numParams; ++paramIdx){
// Skip unfulfilled parameters. There's nothing to do for them.
if (parameterBindings[paramIdx].empty())
continue;
// Determine the parameter type.
const auto &param = params[paramIdx];
auto paramTy = param.getOldType();
// Type sequences ingest the entire set of argument bindings as
// a pack type bound to the sequence archetype for the parameter.
//
// We pull these out special because variadic parameters ban lots of
// the more interesting typing constructs called out below like
// inout and @autoclosure.
if (paramInfo.isVariadicGenericParameter(paramIdx)) {
auto *PET = paramTy->castTo<PackExpansionType>();
OpenTypeSequenceElements openTypeSequence{cs, PET};
for (auto argIdx : parameterBindings[paramIdx]) {
const auto &argument = argsWithLabels[argIdx];
auto argTy = argument.getPlainType();
// First, re-open the parameter type so we bind the elements of the type
// sequence into their proper positions.
auto substParamTy = openTypeSequence.expandParameter();
cs.addConstraint(
subKind, argTy, substParamTy, loc, /*isFavored=*/false);
}
// Now that we have the pack mappings, bind the raw type sequence to the
// whole parameter pack. This ensures references to the type sequence in
// return position refer to the whole pack of elements we just gathered.
std::move(openTypeSequence)
.intoPackTypes([&](TypeVariableType *tsParam, Type pack) {
cs.addConstraint(ConstraintKind::Bind, pack, tsParam, loc,
/*isFavored=*/false);
});
continue;
}
// Skip unfulfilled parameters. There's nothing to do for them.
if (parameterBindings[paramIdx].empty())
continue;
// Compare each of the bound arguments for this parameter.
for (auto argIdx : parameterBindings[paramIdx]) {
auto loc = locator.withPathElement(LocatorPathElt::ApplyArgToParam(
@@ -1505,6 +1617,7 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
}
}
}
if (!argument.isCompileTimeConst() && param.isCompileTimeConst()) {
auto *locator = cs.getConstraintLocator(loc);
SourceRange range;
@@ -1531,6 +1644,7 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
return cs.getTypeMatchSuccess();
}
ConstraintSystem::TypeMatchResult
ConstraintSystem::matchFunctionResultTypes(Type expectedResult, Type fnResult,
TypeMatchOptions flags,
@@ -1753,6 +1867,29 @@ ConstraintSystem::matchTupleTypes(TupleType *tuple1, TupleType *tuple2,
return getTypeMatchSuccess();
}
ConstraintSystem::TypeMatchResult
ConstraintSystem::matchPackTypes(PackType *pack1, PackType *pack2,
ConstraintKind kind, TypeMatchOptions flags,
ConstraintLocatorBuilder locator) {
TypeMatchOptions subflags = getDefaultDecompositionOptions(flags);
if (pack1->getNumElements() != pack2->getNumElements())
return getTypeMatchFailure(locator);
for (unsigned i = 0, n = pack1->getNumElements(); i != n; ++i) {
Type ty1 = pack1->getElementType(i);
Type ty2 = pack2->getElementType(i);
// Compare the element types.
auto result =
matchTypes(ty1, ty2, kind, subflags,
locator.withPathElement(LocatorPathElt::PackElement(i)));
if (result.isFailure())
return result;
}
return getTypeMatchSuccess();
}
/// Check where a representation is a subtype of another.
///
/// The subtype relationship is defined as:
@@ -5780,6 +5917,18 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
// completely different decls, then there's nothing else we can do.
return getTypeMatchFailure(locator);
}
case TypeKind::Pack: {
auto tmpPackLoc = locator.withPathElement(LocatorPathElt::PackType(type1));
auto packLoc = tmpPackLoc.withPathElement(LocatorPathElt::PackType(type2));
return matchPackTypes(cast<PackType>(desugar1),
cast<PackType>(desugar2),
kind, subflags, packLoc);
}
case TypeKind::PackExpansion: {
return matchTypes(cast<PackExpansionType>(desugar1)->getPatternType(),
cast<PackExpansionType>(desugar2)->getPatternType(),
kind, subflags, locator);
}
}
}
@@ -6096,6 +6245,15 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
}
}
}
// A Pack of (Ts...) is convertible to a tuple (X, Y, Z) as long as there
// is an element-wise match.
if (auto *expansionTy = type1->getAs<PackType>()) {
if (!type2->isTypeVariableOrMember()) {
conversionsOrFixes.push_back(
ConversionRestrictionKind::ReifyPackToType);
}
}
}
if (kind >= ConstraintKind::OperatorArgumentConversion) {
@@ -6340,7 +6498,9 @@ ConstraintSystem::simplifyConstructionConstraint(
case TypeKind::Function:
case TypeKind::LValue:
case TypeKind::InOut:
case TypeKind::Module: {
case TypeKind::Module:
case TypeKind::Pack:
case TypeKind::PackExpansion: {
// If solver is in the diagnostic mode and this is an invalid base,
// let's give solver a chance to repair it to produce a good diagnostic.
if (shouldAttemptFixes())