Skip to content
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

[Sema][QoI] Call out missing conformances in protocol witness candidates. #19920

Merged
merged 7 commits into from Oct 22, 2018

Conversation

gregomni
Copy link
Collaborator

Continuing on to try to further improve inference QoI: Let CS::solveSingle() optionally allow fixes in the solution. Use this in protocol inference to look for fixes due to missing conformances and give better candidate failure notes. (Which were previously types-didn't-match notes that didn't really say anything about the actual problem.) Also changed the diagnosis text for the existing associated type inference failure due to missing requirement so that it is phrased similarly.

Thus, new candidate notes like:

a.swift:1:8: error: type 'X' does not conform to protocol 'SetAlgebra'
struct X : OptionSet {}
       ^
Swift.OptionSet:3:37: note: candidate would match if 'X.RawValue' conformed to 'FixedWidthInteger'
    @inlinable public mutating func formUnion(_ other: Self)
a.swift:2:8: error: type 'Y' does not conform to protocol 'Sequence'
struct Y : Sequence {}
       ^
Swift.Sequence:3:20: note: unable to infer associated type 'Iterator' for protocol 'Sequence'
    associatedtype Iterator : IteratorProtocol
                   ^
Swift.Sequence:2:40: note: candidate would match and infer 'Iterator'='Y' if 'Y' conformed to 'IteratorProtocol'
    @inlinable public __consuming func makeIterator() -> Self

…s in protocol inference to give better candidate failure notes.
@gregomni
Copy link
Collaborator Author

@swift-ci Please smoke test.

@gregomni
Copy link
Collaborator Author

@swift-ci Please smoke test.

Copy link
Member

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

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

Cute.

@@ -1782,8 +1782,8 @@ NOTE(bad_associated_type_deduction,none,
"unable to infer associated type %0 for protocol %1",
(DeclName, DeclName))
NOTE(associated_type_deduction_witness_failed,none,
"inferred type %1 (by matching requirement %0) is invalid: "
"does not %select{inherit from|conform to}3 %2",
"candidate would match and infer %0=%1 if %1 "
Copy link
Member

Choose a reason for hiding this comment

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

Spaces around = might be more readable

protocolType);

type = type->mapTypeOutOfContext();
if (auto typeParamTy = type->getAs<GenericTypeParamType>())
Copy link
Member

Choose a reason for hiding this comment

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

What if it’s a dependent member type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Checked for just above (that's one of the things I broke and needed the second commit for)

Copy link
Member

Choose a reason for hiding this comment

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

Should we generalize the DependentMemberType to hasTypeParameter()?

assocType, protocolType);
if (type->isEqual(conformance->getType())) {
if (auto bgt = type->getAs<BoundGenericType>())
type = bgt->getDecl()->getDeclaredType();
Copy link
Member

Choose a reason for hiding this comment

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

What if it’s a non-generic type nested in a generic type? Is this restriction really necessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nope, fix upcoming

@@ -17,7 +17,7 @@ func bad_containers_2(bc: BadContainer2) {
}

struct BadContainer3 : Sequence { // expected-error{{type 'BadContainer3' does not conform to protocol 'Sequence'}}
func makeIterator() { } // expected-note{{inferred type '()' (by matching requirement 'makeIterator()') is invalid: does not conform to 'IteratorProtocol'}}
func makeIterator() { } // expected-note{{candidate would match and infer 'Iterator'='()' if '()' conformed to 'IteratorProtocol'}}
Copy link
Member

Choose a reason for hiding this comment

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

Well, but () is not a nominal type, so it would never conform. Can you instead just say the return type is wrong without offering a suggestion?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, more specific can't-conform-to-non-nominal and can't-inherit-from-non-class coming up.

@slavapestov
Copy link
Member

For completeness you could do something similar for superclass constraints as well... but maybe those are rare enough that it doesn’t matter.

@slavapestov slavapestov self-assigned this Oct 17, 2018
@gregomni
Copy link
Collaborator Author

@swift-ci Please smoke test.

…rements so that we check superclass and same type requirements in the same way.
@gregomni
Copy link
Collaborator Author

@slavapestov Added support for other types of requirements, so it gives similar messages for missing same-type and missing superclass. I was surprised that there was even an existing test that hit the new diagnosis for the same-type one - I thought that would be vanishingly rare.

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

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

These diagnostics are much-improved, thank you!

protocolType);

type = type->mapTypeOutOfContext();
if (auto typeParamTy = type->getAs<GenericTypeParamType>())
Copy link
Member

Choose a reason for hiding this comment

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

Should we generalize the DependentMemberType to hasTypeParameter()?

// If the types would match but for some other missing conformance, find and
// call that out.
if (solution && conformance && solution->Fixes.size() == 1 &&
solution->Fixes.front()->getKind() == FixKind::AddConformance) {
Copy link
Member

Choose a reason for hiding this comment

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

If there is more than one fix, perhaps we should diagnose the first fix? Or walk through the fixes and diagnose the first one we recognize? It won't describe the whole problem, but it's probably a better clue than falling back to "type mismatch."

@@ -1747,9 +1747,26 @@ bool AssociatedTypeInference::diagnoseNoSolutions(
if (failed.Result.isError())
continue;

if (!failed.TypeWitness->is<NominalType>() &&
Copy link
Member

Choose a reason for hiding this comment

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

Please use getAnyNominal() to check whether we have a nominal type here; is<NominalType>() will only identify non-generic nominal types

Copy link
Member

Choose a reason for hiding this comment

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

... ugh, and I guess it's getAnyNominal() && !isExistentialType() because of protocols. The type system's modeling of protocols is not true to the language w.r.t. protocols.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oops, missed the second part of this. Fix momentarily.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wait, I'm confused. I think this is ok because the test is negative. An existential can't conform to a protocol and an existential is not a nominal. A protocol can conform, and a protocol is a nominal. Do I have part of this wrong?

failed.Result.getRequirement());
continue;
}
if (!failed.TypeWitness->is<ClassType>() &&
Copy link
Member

Choose a reason for hiding this comment

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

Please use getClassOrBoundGenericClass() rather than is<ClassType>() here.

continue;
}
if (!failed.TypeWitness->is<ClassType>() &&
!failed.Result.isConformanceRequirement()) {
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, could !failed.Result.isConformanceRequirement() be replaced by a positive check failed.Result.isSuperclassRequirement()? If we get a third failure kind that's "not a conformance requirement" we'd produce the wrong diagnostic here.

@gregomni
Copy link
Collaborator Author

@DougGregor Did those, thanks!

@gregomni
Copy link
Collaborator Author

@swift-ci Please smoke test.

…fix checking into a separate function and try it with multiple fixes, if they exist.
@gregomni
Copy link
Collaborator Author

Figured out my existential confusion, so fixed that, and implemented your suggestion of looking at all fixes, if there is more than one.

@gregomni
Copy link
Collaborator Author

@swift-ci Please smoke test.

@gregomni gregomni merged commit d20fdf5 into apple:master Oct 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants