Add some comments to checkTypeclassSigs()

This commit is contained in:
Sam Vervaeck 2023-05-21 17:53:07 +02:00
parent 56cbfc6fbe
commit 3d19ce988c
Signed by: samvv
SSH key fingerprint: SHA256:dIg0ywU1OP+ZYifrYxy8c5esO72cIKB+4/9wkZj1VaY

View file

@ -843,15 +843,34 @@ namespace bolt {
return Ty;
}
void collectTypeclasses(LetDeclaration* Decl, std::vector<TypeclassSignature>& Out) {
void Checker::checkTypeclassSigs(Node* N) {
struct LetVisitor : CSTVisitor<LetVisitor> {
Checker& C;
void visitLetDeclaration(LetDeclaration* Decl) {
// Will contain the type classes that were specified in the type assertion by the user.
// There might be some other signatures as well, but those are an implementation detail.
std::vector<TypeclassSignature> Expected;
// We must add the type class itself to Expected because in order for
// propagation to work the rigid type variables expect this class to be
// present even inside the current class. By adding it to Expected, we
// are effectively cancelling out the default behavior of requiring the
// presence of this type classes.
if (llvm::isa<ClassDeclaration>(Decl->Parent)) {
auto Class = llvm::cast<ClassDeclaration>(Decl->Parent);
std::vector<TVar *> Tys;
for (auto TE : Class->TypeVars) {
Tys.push_back(llvm::cast<TVar>(TE->getType()));
}
Out.push_back(TypeclassSignature { Class->Name->getCanonicalText(), Tys });
Expected.push_back(
TypeclassSignature{Class->Name->getCanonicalText(), Tys});
}
// Here we scan the type signature for type classes that user expects to be there.
if (Decl->TypeAssert != nullptr) {
if (llvm::isa<QualifiedTypeExpression>(Decl->TypeAssert->TypeExpression)) {
auto QTE = static_cast<QualifiedTypeExpression*>(Decl->TypeAssert->TypeExpression);
@ -864,27 +883,22 @@ namespace bolt {
ZEN_ASSERT(llvm::isa<TVar>(TV));
Tys.push_back(static_cast<TVar*>(TV));
}
Out.push_back(TypeclassSignature { TCE->Name->getCanonicalText(), Tys });
}
Expected.push_back(TypeclassSignature { TCE->Name->getCanonicalText(), Tys });
}
}
}
}
void Checker::checkTypeclassSigs(Node* N) {
struct LetVisitor : CSTVisitor<LetVisitor> {
Checker& C;
void visitLetDeclaration(LetDeclaration* Decl) {
std::vector<TypeclassSignature> Expected;
collectTypeclasses(Decl, Expected);
// Sort them lexically and remove any duplicates
std::sort(Expected.begin(), Expected.end());
Expected.erase(std::unique(Expected.begin(), Expected.end()), Expected.end());
// Will contain the type class signatures that our program inferred that
// at the very least should be present to make the body work.
std::vector<TypeclassSignature> Actual;
// This is ugly but it works. Scan all type variables local to this
// declaration and add the classes that they require to Actual.
for (auto Ty: *Decl->Ctx->TVs) {
auto S = Ty->substitute(C.Solution);
if (llvm::isa<TVar>(S)) {
@ -894,36 +908,55 @@ namespace bolt {
}
}
}
// Sort them lexically and remove any duplicates
std::sort(Actual.begin(), Actual.end());
Actual.erase(std::unique(Actual.begin(), Actual.end()), Actual.end());
auto It1 = Actual.begin();
auto It2 = Expected.begin();
auto ActualIter = Actual.begin();
auto ExpectedIter = Expected.begin();
for (; It1 != Actual.end() || It2 != Expected.end() ;) {
if (It1 == Actual.end()) {
for (; ActualIter != Actual.end() || ExpectedIter != Expected.end() ;) {
// Our program inferred no more type classes that should be present,
// yet Expected still did find a few that the user declared in a
// signature. No errors should be reported, and we can quit this loop.
if (ActualIter == Actual.end()) {
// TODO Maybe issue a warning that a type class went unused
break;
}
if (It2 == Expected.end()) {
for (; It1 != Actual.end(); It1++) {
C.DE.add<TypeclassMissingDiagnostic>(*It1, Decl);
// There are no more type classes that were expected, so any remaining
// type classes in Actual will not have a corresponding signature.
// This should be reported as an error.
if (ExpectedIter == Expected.end()) {
for (; ActualIter != Actual.end(); ActualIter++) {
C.DE.add<TypeclassMissingDiagnostic>(*ActualIter, Decl);
}
break;
}
if (*It1 < *It2) {
// FIXME It1->Ty needs to be unified with potential candidate It2->Ty
C.DE.add<TypeclassMissingDiagnostic>(*It1, Decl);
It1++;
// If ExpectedIter is already at Show, but ActualIter is still at Eq,
// then we clearly missed the Eq in ExpectedIter. This clearly is an
// error, since the user missed something in a type signature.
if (*ActualIter < *ExpectedIter) {
C.DE.add<TypeclassMissingDiagnostic>(*ActualIter, Decl);
ActualIter++;
continue;
}
if (*It2 < *It1) {
// If ActualIter is Show but ExpectedIter is still Eq, then the user
// specified too much type classes in a type signature. This is no error,
// but it might be worthwhile to issue a warning.
if (*ExpectedIter < *ActualIter) {
// DE.add<TypeclassMissingDiagnostic>(It2->Name, Decl);
It2++;
ExpectedIter++;
continue;
}
It1++;
It2++;
// Both type class signatures are equal, cancelling each other out.
ActualIter++;
ExpectedIter++;
}
}