Skip to content

[Requirement Machine] Implement same-element requirements. #67465

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

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ EXPERIMENTAL_FEATURE(NamedOpaqueTypes, false)
EXPERIMENTAL_FEATURE(FlowSensitiveConcurrencyCaptures, false)
EXPERIMENTAL_FEATURE(CodeItemMacros, false)
EXPERIMENTAL_FEATURE(TupleConformances, false)
EXPERIMENTAL_FEATURE(SameElementRequirements, false)

SUPPRESSIBLE_LANGUAGE_FEATURE(ExtensionMacroAttr, 0, "@attached(extension)", true)

Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,9 @@ void PrintAST::printRequirement(const Requirement &req) {
SmallVector<Type, 2> rootParameterPacks;
getTransformedType(req.getFirstType())
->getTypeParameterPacks(rootParameterPacks);
if (req.getKind() != RequirementKind::Layout)
getTransformedType(req.getSecondType())
->getTypeParameterPacks(rootParameterPacks);
bool isPackRequirement = !rootParameterPacks.empty();

switch (req.getKind()) {
Expand Down Expand Up @@ -3256,6 +3259,10 @@ static bool usesFeatureTupleConformances(Decl *decl) {
return false;
}

static bool usesFeatureSameElementRequirements(Decl *decl) {
return false;
}

static bool usesFeatureSymbolLinkageMarkers(Decl *decl) {
auto &attrs = decl->getAttrs();
return std::any_of(attrs.begin(), attrs.end(), [](auto *attr) {
Expand Down
8 changes: 4 additions & 4 deletions lib/AST/GenericEnvironment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,16 @@ struct FindElementArchetypeForOpenedPackParam {
: findElementParam(env, openedPacks), getElementArchetype(env) {}


ElementArchetypeType *operator()(Type interfaceType) {
Type operator()(Type interfaceType) {
assert(interfaceType->isTypeParameter());
if (auto member = interfaceType->getAs<DependentMemberType>()) {
auto baseArchetype = (*this)(member->getBase());
auto baseArchetype = (*this)(member->getBase())
->castTo<ElementArchetypeType>();
return baseArchetype->getNestedType(member->getAssocType())
->castTo<ElementArchetypeType>();
}
assert(interfaceType->is<GenericTypeParamType>());
return getElementArchetype(findElementParam(interfaceType))
->castTo<ElementArchetypeType>();
return getElementArchetype(findElementParam(interfaceType));
}
};

Expand Down
17 changes: 15 additions & 2 deletions lib/AST/GenericSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,16 @@ void GenericSignatureImpl::forEachParam(

for (auto req : getRequirements()) {
GenericTypeParamType *gp;
bool isCanonical = false;
switch (req.getKind()) {
case RequirementKind::SameType: {
if (req.getSecondType()->isParameterPack() !=
req.getFirstType()->isParameterPack()) {
// This is a same-element requirement, which does not make
// type parameters non-canonical.
isCanonical = true;
}

if (auto secondGP = req.getSecondType()->getAs<GenericTypeParamType>()) {
// If two generic parameters are same-typed, then the right-hand one
// is non-canonical.
Expand Down Expand Up @@ -136,7 +144,7 @@ void GenericSignatureImpl::forEachParam(
}

unsigned index = GenericParamKey(gp).findIndexIn(genericParams);
genericParamsAreCanonical[index] = false;
genericParamsAreCanonical[index] = isCanonical;
}

// Call the callback with each parameter and the result of the above analysis.
Expand Down Expand Up @@ -938,7 +946,12 @@ void GenericSignature::verify(ArrayRef<Requirement> reqts) const {
llvm::errs() << "\n";
abort();
}
if (compareDependentTypes(firstType, secondType) >= 0) {
// FIXME: Same-element rewrite rules are of the form [element].T => U,
// which turns into a same-type requirement repeat U == each T. The
// pack element must always be on the left side of the rule, so type
// parameters can appear out of order in the final same-type requirement.
if (firstType->isParameterPack() == secondType->isParameterPack() &&
compareDependentTypes(firstType, secondType) >= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you change RequirementBuilder.cpp to build them in the other order instead?

llvm::errs() << "Out-of-order type parameters: ";
reqt.dump(llvm::errs());
llvm::errs() << "\n";
Expand Down
3 changes: 3 additions & 0 deletions lib/AST/RequirementMachine/GenericSignatureQueries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ RequirementMachine::getLongestValidPrefix(const MutableTerm &term) const {
case Symbol::Kind::ConcreteType:
case Symbol::Kind::ConcreteConformance:
case Symbol::Kind::Shape:
case Symbol::Kind::PackElement:
llvm::errs() <<"Invalid symbol in a type term: " << term << "\n";
abort();
}
Expand Down Expand Up @@ -788,6 +789,7 @@ void RequirementMachine::verify(const MutableTerm &term) const {
switch (symbol.getKind()) {
case Symbol::Kind::Protocol:
case Symbol::Kind::GenericParam:
case Symbol::Kind::PackElement:
erased.add(symbol);
continue;

Expand Down Expand Up @@ -827,6 +829,7 @@ void RequirementMachine::verify(const MutableTerm &term) const {
case Symbol::Kind::Superclass:
case Symbol::Kind::ConcreteType:
case Symbol::Kind::ConcreteConformance:
case Symbol::Kind::PackElement:
llvm::errs() << "Bad interior symbol " << symbol << " in " << term << "\n";
abort();
break;
Expand Down
3 changes: 2 additions & 1 deletion lib/AST/RequirementMachine/HomotopyReduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,8 @@ RewriteSystem::getMinimizedGenericSignatureRules() const {
continue;
}

if (rule.getLHS()[0].getKind() != Symbol::Kind::GenericParam)
if (rule.getLHS()[0].getKind() != Symbol::Kind::PackElement &&
rule.getLHS()[0].getKind() != Symbol::Kind::GenericParam)
continue;

rules.push_back(ruleID);
Expand Down
3 changes: 3 additions & 0 deletions lib/AST/RequirementMachine/InterfaceType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ getTypeForSymbolRange(const Symbol *begin, const Symbol *end,
// member type rooted at Self; handle the associated type below.
break;

case Symbol::Kind::PackElement:
continue;

case Symbol::Kind::Name:
case Symbol::Kind::Layout:
case Symbol::Kind::Superclass:
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/RequirementMachine/MinimalConformances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ static const ProtocolDecl *getParentConformanceForTerm(Term lhs) {
case Symbol::Kind::ConcreteType:
case Symbol::Kind::ConcreteConformance:
case Symbol::Kind::Shape:
case Symbol::Kind::PackElement:
break;
}

Expand Down Expand Up @@ -552,6 +553,7 @@ void RewriteSystem::computeCandidateConformancePaths(
//
// where Y is the simplified form of X.W.
} else if (rhs.isAnyConformanceRule() &&
!lhs.isSameElementRule() &&
(unsigned)(lhs.getLHS().end() - from) < rhs.getLHS().size()) {
if (Debug.contains(DebugFlags::MinimalConformancesDetail)) {
llvm::dbgs() << "Case 2: same-type suffix\n";
Expand Down
1 change: 1 addition & 0 deletions lib/AST/RequirementMachine/PropertyUnification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ void PropertyMap::addProperty(
case Symbol::Kind::GenericParam:
case Symbol::Kind::AssociatedType:
case Symbol::Kind::Shape:
case Symbol::Kind::PackElement:
break;
}

Expand Down
1 change: 1 addition & 0 deletions lib/AST/RequirementMachine/RequirementBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ void RequirementBuilder::addRequirementRules(ArrayRef<unsigned> rules) {
case Symbol::Kind::AssociatedType:
case Symbol::Kind::GenericParam:
case Symbol::Kind::Shape:
case Symbol::Kind::PackElement:
break;
}

Expand Down
17 changes: 10 additions & 7 deletions lib/AST/RequirementMachine/RequirementLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,16 @@ static void desugarSameTypeRequirement(Requirement req, SourceLoc loc,
break;
}

// If one side is a parameter pack, this is a same-element requirement, which
// is not yet supported.
if (firstType->isParameterPack() != secondType->isParameterPack()) {
errors.push_back(RequirementError::forSameElement(
{kind, sugaredFirstType, secondType}, loc));
recordedErrors = true;
return true;
auto &ctx = firstType->getASTContext();
if (!ctx.LangOpts.hasFeature(Feature::SameElementRequirements)) {
// If one side is a parameter pack, this is a same-element requirement, which
// is not yet supported.
if (firstType->isParameterPack() != secondType->isParameterPack()) {
errors.push_back(RequirementError::forSameElement(
{kind, sugaredFirstType, secondType}, loc));
recordedErrors = true;
return true;
}
}

if (firstType->isTypeParameter() && secondType->isTypeParameter()) {
Expand Down
2 changes: 1 addition & 1 deletion lib/AST/RequirementMachine/RequirementMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ void RequirementMachine::dump(llvm::raw_ostream &out) const {
for (auto paramTy : Params) {
out << " " << Type(paramTy);
if (paramTy->isParameterPack())
out << "…";
out << " " << paramTy;
}
out << " >";
}
Expand Down
12 changes: 6 additions & 6 deletions lib/AST/RequirementMachine/RequirementMachineRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -963,13 +963,13 @@ InferredGenericSignatureRequest::evaluate(
if (reduced->hasError() || reduced->isEqual(genericParam))
continue;

if (reduced->isTypeParameter()) {
// If one side is a parameter pack and the other is not, this is a
// same-element requirement that cannot be expressed with only one
// type parameter.
if (genericParam->isParameterPack() != reduced->isParameterPack())
continue;
// If one side is a parameter pack and the other is not, this is a
// same-element requirement that cannot be expressed with only one
// type parameter.
if (genericParam->isParameterPack() != reduced->isParameterPack())
continue;

if (reduced->isTypeParameter()) {
ctx.Diags.diagnose(loc, diag::requires_generic_params_made_equal,
genericParam, result->getSugaredType(reduced))
.warnUntilSwiftVersion(6);
Expand Down
3 changes: 3 additions & 0 deletions lib/AST/RequirementMachine/RewriteContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class RewriteContext final {
/// The singleton storage for shape symbols.
Symbol::Storage *TheShapeSymbol;

/// The singleton storage for pack element symbols.
Symbol::Storage *ThePackElementSymbol;

/// Folding set for uniquing terms.
llvm::FoldingSet<Term::Storage> Terms;

Expand Down
8 changes: 6 additions & 2 deletions lib/AST/RequirementMachine/RewriteSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,9 @@ void RewriteSystem::verifyRewriteRules(ValidityPolicy policy) const {
}

if (index != 0) {
ASSERT_RULE(symbol.getKind() != Symbol::Kind::GenericParam);
ASSERT_RULE(symbol.getKind() != Symbol::Kind::GenericParam ||
(index == 1 &&
lhs[index - 1].getKind() == Symbol::Kind::PackElement));
}

if (!rule.isLHSSimplified() &&
Expand Down Expand Up @@ -624,7 +626,9 @@ void RewriteSystem::verifyRewriteRules(ValidityPolicy policy) const {
}

if (index != 0) {
ASSERT_RULE(symbol.getKind() != Symbol::Kind::GenericParam);
ASSERT_RULE(symbol.getKind() != Symbol::Kind::GenericParam ||
(index == 1 &&
lhs[index - 1].getKind() == Symbol::Kind::PackElement));
}

if (!rule.isRHSSimplified() &&
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/RequirementMachine/Rule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const ProtocolDecl *Rule::isAnyConformanceRule() const {
case Symbol::Kind::AssociatedType:
case Symbol::Kind::GenericParam:
case Symbol::Kind::Shape:
case Symbol::Kind::PackElement:
break;
}

Expand Down Expand Up @@ -146,6 +147,11 @@ bool Rule::isCircularConformanceRule() const {
return true;
}

/// Returns \c true if this rule is prefixed with the \c [element] symbol.
bool Rule::isSameElementRule() const {
return LHS[0].getKind() == Symbol::Kind::PackElement;
}

/// A protocol typealias rule takes one of the following two forms,
/// where T is a name symbol:
///
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/RequirementMachine/Rule.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ class Rule final {

bool isCircularConformanceRule() const;

bool isSameElementRule() const;

/// See above for an explanation of these predicates.
bool isPermanent() const {
return Permanent;
Expand Down
27 changes: 26 additions & 1 deletion lib/AST/RequirementMachine/RuleBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ void RuleBuilder::addRequirement(const Requirement &req,

case RequirementKind::SameType: {
auto otherType = CanType(req.getSecondType());
auto elementSymbol = Symbol::forPackElement(Context);

if (!otherType->isTypeParameter()) {
// A concrete same-type requirement T == C<X, Y> becomes a
Expand All @@ -380,6 +381,16 @@ void RuleBuilder::addRequirement(const Requirement &req,
: Context.getSubstitutionSchemaFromType(
otherType, proto, result));

// If 'T' is a parameter pack, this is a same-element
// requirement that becomes the following rewrite rule:
//
// [element].T.[concrete: C<X, Y>] => [element].T
if (subjectType->isParameterPack()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

But only if the constraint type does not contain any packs right?

Copy link
Member Author

@hborla hborla Aug 31, 2023

Choose a reason for hiding this comment

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

Yeah, that's right. I'm not sure what the rewrite rule looks like if the constraint type contains packs but the subject type does not? That's still conceptually a same-element requirement. Slava reminded me that something like repeat T == Array<each U> is invalid because it's not representable!

llvm::SmallVector<Symbol, 3> subjectSymbols{elementSymbol};
subjectSymbols.append(subjectTerm.begin(), subjectTerm.end());
subjectTerm = MutableTerm(std::move(subjectSymbols));
}

constraintTerm = subjectTerm;
constraintTerm.add(Symbol::forConcreteType(otherType, result, Context));
break;
Expand All @@ -390,6 +401,20 @@ void RuleBuilder::addRequirement(const Requirement &req,
otherType, *substitutions)
: Context.getMutableTermForType(
otherType, proto));

if (subjectType->isParameterPack() != otherType->isParameterPack()) {
// This is a same-element requirement.
llvm::SmallVector<Symbol, 3> symbols{elementSymbol};

if (subjectType->isParameterPack()) {
symbols.append(subjectTerm.begin(), subjectTerm.end());
subjectTerm = MutableTerm(std::move(symbols));
} else {
symbols.append(constraintTerm.begin(), constraintTerm.end());
constraintTerm = MutableTerm(std::move(symbols));
}
}

break;
}
}
Expand Down Expand Up @@ -565,4 +590,4 @@ void RuleBuilder::collectPackShapeRules(ArrayRef<GenericTypeParamType *> generic
}
}
}
}
}
Loading