Skip to content

Commit 9a0c87b

Browse files
committed
RequirementMachine: Implement GenericSignature::getCanonicalTypeInContext() query
We compute the canonical type by first simplifying the type term, and then checking if it is a concrete type. If there's no concrete type, we convert the simplified term back to an interface type and return that; otherwise, we canonicalize any structural sub-components of the concrete type that contain interface types, and so on. Due to a quirk of how the existing declaration checker works, we also need to handle "purely concrete" member types, eg if I have a signature `<T where T == Foo>`, and we're asked to canonicalize the type `T.[P:A]` where Foo : A. This comes up because we can derive the signature `<T where T == Foo>` from a generic signature like `<T where T : P>`; adding the concrete requirement 'T == Foo' renders 'T : P' redundant. We then want to take interface types written against the original signature and canonicalize them with respect to the derived signature. The problem is that `T.[P:A]` is not a valid term in the rewrite system for `<T where T == Foo>`, since we do not have the requirement T : P. A more principled solution would build a substitution map when building a derived generic signature that adds new requirements; interface types would first be substituted before being canonicalized in the new signature. For now, we handle this with a two-step process; we split a term up into a longest valid prefix, which must resolve to a concrete type, and the remaining suffix, which we use to perform a concrete substitution using subst().
1 parent f0d59d5 commit 9a0c87b

File tree

3 files changed

+217
-2
lines changed

3 files changed

+217
-2
lines changed

include/swift/AST/RequirementMachine.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace swift {
2424
class ASTContext;
2525
class AssociatedTypeDecl;
2626
class CanType;
27+
class GenericTypeParamType;
2728
class LayoutConstraint;
2829
class ProtocolDecl;
2930
class Requirement;
@@ -61,6 +62,8 @@ class RequirementMachine final {
6162
GenericSignature::RequiredProtocols getRequiredProtocols(Type depType) const;
6263
bool isConcreteType(Type depType) const;
6364
bool areSameTypeParameterInContext(Type depType1, Type depType2) const;
65+
Type getCanonicalTypeInContext(Type type,
66+
TypeArrayView<GenericTypeParamType> genericParams) const;
6467

6568
void dump(llvm::raw_ostream &out) const;
6669
};

lib/AST/GenericSignature.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -743,8 +743,41 @@ CanType GenericSignatureImpl::getCanonicalTypeInContext(Type type) const {
743743
if (!type->hasTypeParameter())
744744
return CanType(type);
745745

746-
auto &builder = *getGenericSignatureBuilder();
747-
return builder.getCanonicalTypeInContext(type, { })->getCanonicalType();
746+
auto computeViaGSB = [&]() {
747+
auto &builder = *getGenericSignatureBuilder();
748+
return builder.getCanonicalTypeInContext(type, { })->getCanonicalType();
749+
};
750+
751+
auto computeViaRQM = [&]() {
752+
auto *machine = getRequirementMachine();
753+
return machine->getCanonicalTypeInContext(type, { })->getCanonicalType();
754+
};
755+
756+
auto &ctx = getASTContext();
757+
if (ctx.LangOpts.EnableRequirementMachine) {
758+
auto rqmResult = computeViaRQM();
759+
760+
#ifndef NDEBUG
761+
auto gsbResult = computeViaGSB();
762+
763+
if (gsbResult != rqmResult) {
764+
llvm::errs() << "RequirementMachine::getCanonicalTypeInContext() is broken\n";
765+
llvm::errs() << "Generic signature: " << GenericSignature(this) << "\n";
766+
llvm::errs() << "Dependent type: "; type.dump(llvm::errs());
767+
llvm::errs() << "GenericSignatureBuilder says: " << gsbResult << "\n";
768+
gsbResult.dump(llvm::errs());
769+
llvm::errs() << "RequirementMachine says: " << rqmResult << "\n";
770+
rqmResult.dump(llvm::errs());
771+
llvm::errs() << "\n";
772+
getRequirementMachine()->dump(llvm::errs());
773+
abort();
774+
}
775+
#endif
776+
777+
return rqmResult;
778+
} else {
779+
return computeViaGSB();
780+
}
748781
}
749782

750783
ArrayRef<CanTypeWrapper<GenericTypeParamType>>

lib/AST/RequirementMachine/RequirementMachine.cpp

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ struct RequirementMachine::Implementation {
238238
Map(Context, System.getProtocols()) {}
239239
void verify(const MutableTerm &term);
240240
void dump(llvm::raw_ostream &out);
241+
242+
MutableTerm getLongestValidPrefix(const MutableTerm &term);
241243
};
242244

243245
void RequirementMachine::Implementation::verify(const MutableTerm &term) {
@@ -533,3 +535,180 @@ bool RequirementMachine::areSameTypeParameterInContext(Type depType1,
533535

534536
return (term1 == term2);
535537
}
538+
539+
MutableTerm
540+
RequirementMachine::Implementation::getLongestValidPrefix(const MutableTerm &term) {
541+
MutableTerm prefix;
542+
543+
for (auto atom : term) {
544+
switch (atom.getKind()) {
545+
case Atom::Kind::Name:
546+
return prefix;
547+
548+
case Atom::Kind::Protocol:
549+
assert(prefix.empty() &&
550+
"Protocol atom can only appear at the start of a type term");
551+
if (!System.getProtocols().isKnownProtocol(atom.getProtocol()))
552+
return prefix;
553+
554+
break;
555+
556+
case Atom::Kind::GenericParam:
557+
assert(prefix.empty() &&
558+
"Generic parameter atom can only appear at the start of a type term");
559+
break;
560+
561+
case Atom::Kind::AssociatedType: {
562+
const auto *equivClass = Map.lookUpEquivalenceClass(prefix);
563+
if (!equivClass)
564+
return prefix;
565+
566+
auto conformsTo = equivClass->getConformsTo();
567+
568+
for (const auto *proto : atom.getProtocols()) {
569+
if (!System.getProtocols().isKnownProtocol(proto))
570+
return prefix;
571+
572+
// T.[P:A] is valid iff T conforms to P.
573+
if (std::find(conformsTo.begin(), conformsTo.end(), proto)
574+
== conformsTo.end())
575+
return prefix;
576+
}
577+
578+
break;
579+
}
580+
581+
case Atom::Kind::Layout:
582+
case Atom::Kind::Superclass:
583+
case Atom::Kind::ConcreteType:
584+
llvm_unreachable("Property atom cannot appear in a type term");
585+
}
586+
587+
// This atom is valid, add it to the longest prefix.
588+
prefix.add(atom);
589+
}
590+
591+
return prefix;
592+
}
593+
594+
/// Unlike the other queries, the input type can be any type, not just a
595+
/// type parameter.
596+
///
597+
/// Replaces all structural components that are type parameters with their
598+
/// most canonical form, which is either a (possibly different)
599+
/// type parameter, or a concrete type, in which case we recursively
600+
/// simplify any type parameters appearing in structural positions of
601+
/// that concrete type as well, and so on.
602+
Type RequirementMachine::getCanonicalTypeInContext(
603+
Type type,
604+
TypeArrayView<GenericTypeParamType> genericParams) const {
605+
const auto &protos = Impl->System.getProtocols();
606+
607+
return type.transformRec([&](Type t) -> Optional<Type> {
608+
if (!t->isTypeParameter())
609+
return None;
610+
611+
// Get a simplified term T.
612+
auto term = Impl->Context.getMutableTermForType(t->getCanonicalType(),
613+
/*proto=*/nullptr);
614+
Impl->System.simplify(term);
615+
616+
// We need to handle "purely concrete" member types, eg if I have a
617+
// signature <T where T == Foo>, and we're asked to canonicalize the
618+
// type T.[P:A] where Foo : A.
619+
//
620+
// This comes up because we can derive the signature <T where T == Foo>
621+
// from a generic signature like <T where T : P>; adding the
622+
// concrete requirement 'T == Foo' renders 'T : P' redundant. We then
623+
// want to take interface types written against the original signature
624+
// and canonicalize them with respect to the derived signature.
625+
//
626+
// The problem is that T.[P:A] is not a valid term in the rewrite system
627+
// for <T where T == Foo>, since we do not have the requirement T : P.
628+
//
629+
// A more principled solution would build a substitution map when
630+
// building a derived generic signature that adds new requirements;
631+
// interface types would first be substituted before being canonicalized
632+
// in the new signature.
633+
//
634+
// For now, we handle this with a two-step process; we split a term up
635+
// into a longest valid prefix, which must resolve to a concrete type,
636+
// and the remaining suffix, which we use to perform a concrete
637+
// substitution using subst().
638+
639+
// In the below, let T be a type term, with T == UV, where U is the
640+
// longest valid prefix.
641+
//
642+
// Note that V can be empty if T is fully valid; we expect this to be
643+
// true most of the time.
644+
auto prefix = Impl->getLongestValidPrefix(term);
645+
646+
// Get a type (concrete or dependent) for U.
647+
auto prefixType = [&]() -> Type {
648+
Impl->verify(prefix);
649+
650+
auto *equivClass = Impl->Map.lookUpEquivalenceClass(prefix);
651+
if (equivClass && equivClass->isConcreteType()) {
652+
auto concreteType = equivClass->getConcreteType(genericParams,
653+
protos, Impl->Context);
654+
if (!concreteType->hasTypeParameter())
655+
return concreteType;
656+
657+
// FIXME: Recursion guard is needed here
658+
return getCanonicalTypeInContext(concreteType, genericParams);
659+
}
660+
661+
return Impl->Context.getTypeForTerm(prefix, genericParams, protos);
662+
}();
663+
664+
// If T is already valid, the longest valid prefix U of T is T itself, and
665+
// V is empty. Just return the type we computed above.
666+
//
667+
// This is the only case where U is allowed to be dependent.
668+
if (prefix.size() == term.size())
669+
return prefixType;
670+
671+
// If U is not concrete, we have an invalid member type of a dependent
672+
// type, which is not valid in this generic signature. Give up.
673+
if (prefixType->isTypeParameter()) {
674+
llvm::errs() << "Invalid type parameter in getCanonicalTypeInContext()\n";
675+
llvm::errs() << "Original type: " << type << "\n";
676+
llvm::errs() << "Simplified term: " << term << "\n";
677+
llvm::errs() << "Longest valid prefix: " << prefix << "\n";
678+
llvm::errs() << "Prefix type: " << prefixType << "\n";
679+
llvm::errs() << "\n";
680+
dump(llvm::errs());
681+
abort();
682+
}
683+
684+
// Compute the type of the unresolved suffix term V, rooted in the
685+
// generic parameter τ_0_0.
686+
auto origType = Impl->Context.getRelativeTypeForTerm(
687+
term, prefix, Impl->System.getProtocols());
688+
689+
// Substitute τ_0_0 in the above relative type with the concrete type
690+
// for U.
691+
//
692+
// Example: if T == A.B.C and the longest valid prefix is A.B which
693+
// maps to a concrete type Foo<Int>, then we have:
694+
//
695+
// U == A.B
696+
// V == C
697+
//
698+
// prefixType == Foo<Int>
699+
// origType == τ_0_0.C
700+
// substType == Foo<Int>.C
701+
//
702+
auto substType = origType.subst(
703+
[&](SubstitutableType *type) -> Type {
704+
assert(cast<GenericTypeParamType>(type)->getDepth() == 0);
705+
assert(cast<GenericTypeParamType>(type)->getIndex() == 0);
706+
707+
return prefixType;
708+
},
709+
LookUpConformanceInSignature(Impl->Sig.getPointer()));
710+
711+
// FIXME: Recursion guard is needed here
712+
return getCanonicalTypeInContext(substType, genericParams);
713+
});
714+
}

0 commit comments

Comments
 (0)