-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[CSDiagnostics] Add diagnostics for holes in generic parameter packs #71701
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
Conversation
One more thing - please add test-cases from description to |
@li3zhen1 |
@li3zhen1 So I've been looking at the the constraint system state with these problematic cases and I'm thinking that we should just produce a tailored fixed for each pack element that cannot be inferred with diagnostic like 'cannot infer pack element # from context It seems reasonable to let pack element to be bound to a hole and propagate that to the |
lib/Sema/CSBindings.cpp
Outdated
if (isExpr<NilLiteralExpr>(eltDstLocator->getAnchor())) { | ||
ConstraintFix *fix = | ||
SpecifyContextualTypeForNil::create(cs, eltDstLocator); | ||
return std::make_pair(fix, /*impact=*/(unsigned)10); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested this with your fix from #71756 , everything should be good now:
/Users/lizhen/Projects/swift-debug/generic_pack.swift:5:19: error: 'nil' requires a contextual type
_ = foo(value: 1, nil)
^
/Users/lizhen/Projects/swift-debug/generic_pack.swift:11:34: error: 'nil' requires a contextual type
bar(1, 5, "Hello", foo(value: 1, nil), u: 3, w: 4, 8, nil)
^
/Users/lizhen/Projects/swift-debug/generic_pack.swift:11:55: error: 'nil' requires a contextual type
bar(1, 5, "Hello", foo(value: 1, nil), u: 3, w: 4, 8, nil)
^
/Users/lizhen/Projects/swift-debug/generic_pack.swift:19:7: error: 'nil' requires a contextual type
_ = S(nil)
^
/Users/lizhen/Projects/swift-debug/generic_pack.swift:20:12: error: 'nil' requires a contextual type
_ = S.init(nil)
^
/Users/lizhen/Projects/swift-debug/generic_pack.swift:22:10: error: 'nil' requires a contextual type
_ = S(1, nil)
^
/Users/lizhen/Projects/swift-debug/generic_pack.swift:23:15: error: 'nil' requires a contextual type
_ = S.init(2, nil)
^
lib/Sema/CSBindings.cpp
Outdated
} | ||
|
||
// The hole in a generic pack parameter can not be inferred | ||
ConstraintFix *fix = SpecifyTypeForPlaceholder::create(cs, eltDstLocator); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't think of an example that triggers cannot infer pack element # from context
, could you name some?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I was trying to say in my previous comment is that I think we need to add a new ConstraintFix and a new diagnostic to accompany it and record it in TypeVariableBinding::fixForHole
when the dstLocator ends with PackElement
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, just added new diagnostics could not infer pack element #%0 from context
. But I'm still using unresolved_nil_literal
for nil cases. Do we need a new diag for nil?
TVO_CanBindToHole
flag in ConstraintSystem::openGenericParameter
when generic parameter is parameter pack
How about this?
|
Sorry, I'm trying to decide how I keep about all of the locators manipulation that is going on here. |
lib/Sema/CSDiagnostics.cpp
Outdated
const auto* locator = getLocator(); | ||
auto path = locator->getPath(); | ||
|
||
if (path.size() > 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way everything is setup today I don't think you need any of this checking in the path
. The fix is always going to be with locator that ends on PackElement
and simplifyLocator
changes take care of resolving anchor
correctly, so I think this whole method could be simplified down to:
auto *locator = getLocator();
auto *elementLoc = locator->castLastElementTo<LocatorPathElt::PackElement>();
if (isExpr<NilLiteralExpr>(getAnchor())) {
emitDiagnostic(diag::unresolved_nil_literal);
} else {
// unable to infer the type of an element of generic pack params
emitDiagnostic(diag::could_not_infer_pack_element, elementLoc->getIndex());
}
if (auto *applyExpr = getAsExpr<ApplyExpr>(locator->getAnchor())) {
// use getCalleeLocator + getOverloadChoice to find overload and note
}
return true;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func patternInstantiationTupleTest1<each T>() -> (repeat Array<each T>) {}
func patternInstantiationConcreteInvalid() {
let _: Set<Int> = patternInstantiationTupleTest1()
// expected-error@-1 {{cannot convert value of type '(repeat Array<_>)' to specified type 'Set<Int>'}}
let _: (Array<Int>, Set<String>) = patternInstantiationTupleTest1() // expected-error {{'(repeat Array<Int, _>)' is not convertible to '(Array<Int>, Set<String>)', tuples have a different number of elements}}
}
Seems like this case in swift/test/Constraints/pack_expansion_types.swift
unintentionally hits diag::could_not_infer_pack_element
branch without the guarding conditions.
Fixes:
[fix: ignore specified contextual type] @ locator@0x1339e7800 [Call@/Users/lizhen/Projects/swift-debug/boldly.swift:4:21 → contextual type]
[fix: specify pack element type] @ locator@0x1339e7a68 [Call@/Users/lizhen/Projects/swift-debug/boldly.swift:4:21 → contextual type → tuple type '(/* shape: $T1 */ repeat Array<$T1>)' → tuple type '(_: Set<Int>)' → tuple element #0 → pack element #0]
(failed constraint (/* shape: Pack{<<unresolvedtype>>} */ repeat Array<Pack{<<unresolvedtype>>}>) conv Set<Int> @ locator@0x134015400 [])
(increasing 'user conversion' score by 1 @ locator@0x134015400 [])
(failed constraint (/* shape: Pack{<<unresolvedtype>>} */ repeat Array<Pack{<<unresolvedtype>>}>) bridging conv Set<Int> @ locator@0x134015400 [])
/Users/lizhen/Projects/swift-debug/boldly.swift:4:21: error: cannot convert value of type '(repeat Array<_>)' to specified type 'Set<Int>'
let _: Set<Int> = patternInstantiationTupleTest1()
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/lizhen/Projects/swift-debug/boldly.swift:4:21: error: could not infer pack element #0 from context
let _: Set<Int> = patternInstantiationTupleTest1()
^
Guess I should add some guards like this?
const auto applyArgToParamElt = (path.end() - 2)->getAs<LocatorPathElt::ApplyArgToParam>();
const auto packElementElt = (path.end() - 1)->getAs<LocatorPathElt::PackElement>();
if (!applyArgToParamElt || !packElementElt) {
return false;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's okay, the conversion fails and the element cannot be inferred because of that. For that particular case we could detect that it's mismatch between collections and propagate the element type down to avoid the second diagnostic.
…t overloads and update test cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to 0-based indexing since I found other sema diagnostics are doing so.
lib/Sema/CSDiagnostics.cpp
Outdated
const auto packElementElt = | ||
(path.end() - 1)->getAs<LocatorPathElt::PackElement>(); | ||
if (!applyArgToParamElt || !packElementElt) { | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't produce a fix in this case because this would just result in a fallback diagnostic this looks like more of an assert to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For that particular case we could detect that it's mismatch between collections and propagate the element type down to avoid the second diagnostic.
Could you give more hint about this? I'm not sure about how to kick this out:
contextual type → tuple type → tuple type → tuple element #0 → pack element #0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so we are on the same page here - you don't have to do it in this PR. This would have to be detected during fixing of the conversion mismatch that's when we have both Set and Array types, we can have a special case in repairFailures for collections
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll try to look into this. Just one more question – is this supposed to be a generic type mismatch or the specific case for collections?
This could also invoke the same diagnostics with the same path:
struct S<T> {
init() {}
}
struct S2<T> {
init() {}
}
func patternInstantiationTupleTest1<each T>() -> (repeat S<each T>) {}
func patternInstantiationConcreteInvalid() {
let _: S2<Int> = patternInstantiationTupleTest1()
let _: (S<Int>, S2<String>) = patternInstantiationTupleTest1()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, maybe it's okay to forward generic arguments upon mismatches in general if the number of expected arguments on the other side matches, so if both types are BoundGenericType
s with the same number of arguments
@li3zhen1 We are getting pretty close. I think all we need is to address a few remaining comments and add a few more test-cases that test newly add diagnostic. |
@xedin Sorry for the delay. I'm still trying to learn how |
No rush! Regarding repairFailures, there is a simpler problem you can look at which doesn't involve variadic generics:
|
The failure to convert This block of code is |
There should be no |
But as I mentioned before, you don't have to fix it in this PR - you can just adjust the changed diagnostics, we'll take a look what changed and whether it makes sense. |
So does this mean that I should move the condition if (dstLocator->isLastElement<LocatorPathElt::PackElement>()) {
if (applyArguments -> applyArgToParam -> PackElements) {
// A hole appears as an element of generic pack params
ConstraintFix *Fix = SpecifyPackElementType::create(cs, dstLocator);
return std::make_pair(Fix, defaultImpact);
}
return std::nullopt;
} |
In general if there is a case that cannot be diagnosed, it shouldn't produce a fix just to fail a diagnostic. If we detect that a type variable represents a pack element it shouldn't matter what path it uses because it can always fallback to newly added "cannot infer pack element ..." diagnostic right? In the diagnostic we could check for presence of |
If I understand correctly, I need to add the fallback diagnostic, and either:
Right? |
Not really, I think check for PackElement can be turned into an assert and check for apply arguments could be removed completely for the diagnostic. |
I see. Below is the result accordingly. Guess I can't pass the test without fixing that placeholder?
bool UnableToInferGenericPackElementType::diagnoseAsError() {
auto *locator = getLocator();
auto path = locator->getPath();
const auto packElementElt = (path.end() - 1)->getAs<LocatorPathElt::PackElement>();
if (isExpr<NilLiteralExpr>(getAnchor())) {
emitDiagnostic(diag::unresolved_nil_literal);
} else {
// ❌ generic mismatch hits here:
emitDiagnostic(diag::could_not_infer_pack_element,
packElementElt->getIndex());
}
if (isExpr<ApplyExpr>(locator->getAnchor())) {
// emit callee side diagnostics
if (auto *calleeLocator = getSolution().getCalleeLocator(locator)) {
if (const auto choice = getOverloadChoiceIfAvailable(calleeLocator)) {
if (auto *decl = choice->choice.getDeclOrNull()) {
const auto applyArgToParamElt =
(path.end() - 2)->getAs<LocatorPathElt::ApplyArgToParam>();
if (auto paramDecl =
getParameterAt(decl, applyArgToParamElt->getParamIdx())) {
emitDiagnosticAt(
paramDecl->getLoc(), diag::note_in_opening_pack_element,
packElementElt->getIndex(), paramDecl->getNameStr());
}
}
}
}
}
return true;
} |
lib/Sema/CSDiagnostics.cpp
Outdated
if (llvm::any_of(getSolution().Fixes, [&locator](const ConstraintFix *fix) { | ||
return fix->getLocator() == locator; | ||
})) | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what are you trying to archive here, this would always return false
because locator for diagnostic comes from the fix. What I was saying in my previous comment is that we need
auto packElementElt = locator->getLastElementAs<PackElement>();
assert(packElementElt);
That that's pretty much it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func patternInstantiationTupleTest1<each T>() -> (repeat Array<each T>) {}
func patternInstantiationConcreteInvalid() {
let _: Set<Int> = patternInstantiationTupleTest1()
// expected-error@-1 {{cannot convert value of type '(repeat Array<_>)' to specified type 'Set<Int>'}}
let _: (Array<Int>, Set<String>) = patternInstantiationTupleTest1() // expected-error {{'(repeat Array<Int, _>)' is not convertible to '(Array<Int>, Set<String>)', tuples have a different number of elements}}
}
This case produces 2 fix here:
Fixes:
[fix: ignore specified contextual type] @ locator@0x1339e7800 [Call@/Users/lizhen/Projects/swift-debug/boldly.swift:4:21 → contextual type]
[fix: specify pack element type] @ locator@0x1339e7a68 [Call@/Users/lizhen/Projects/swift-debug/boldly.swift:4:21 → contextual type → tuple type '(/* shape: $T1 */ repeat Array<$T1>)' → tuple type '(_: Set<Int>)' → tuple element #0 → pack element #0]
(failed constraint (/* shape: Pack{<<unresolvedtype>>} */ repeat Array<Pack{<<unresolvedtype>>}>) conv Set<Int> @ locator@0x134015400 [])
(increasing 'user conversion' score by 1 @ locator@0x134015400 [])
(failed constraint (/* shape: Pack{<<unresolvedtype>>} */ repeat Array<Pack{<<unresolvedtype>>}>) bridging conv Set<Int> @ locator@0x134015400 [])
/Users/lizhen/Projects/swift-debug/boldly.swift:4:21: error: cannot convert value of type '(repeat Array<_>)' to specified type 'Set<Int>'
let _: Set<Int> = patternInstantiationTupleTest1()
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/lizhen/Projects/swift-debug/boldly.swift:4:21: error: could not infer pack element #0 from context
let _: Set<Int> = patternInstantiationTupleTest1()
^
I was trying to mute the second fix and it seems work well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, let's update the test for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It passes tests for now. Is there any thing I need to add?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that it's passing tests but the code is nevertheless wrong and that statement on top of this comment chain has to be removed and tests updated to add new diagnostic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated. Sorry I misunderstood that
lib/Sema/CSDiagnostics.cpp
Outdated
auto *locator = getLocator(); | ||
auto path = locator->getPath(); | ||
|
||
auto packElementElt = (path.end() - 1)->getAs<LocatorPathElt::PackElement>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We don't actually need to do this arithmetic - use locator->getLastElementAs<LocatorPathElt::PackElement>()
instead.
@@ -1,4 +1,4 @@ | |||
// RUN: %target-typecheck-verify-swift | |||
// RUN: %target-typecheck-verify-swift -disable-availability-checking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is -disable-availability-checking
necessary now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The struct case emit the following error on my end:
/Users/lizhen/Projects/swift-projects/swift/test/Constraints/variadic_generic_functions.swift:107:10: error: unexpected note produced: add @available attribute to enclosing generic struct
struct Foo<each T> {
^
/Users/lizhen/Projects/swift-projects/swift/test/Constraints/variadic_generic_functions.swift:107:14: error: unexpected error produced: parameter packs in generic types are only available in macOS 14.0.0 or newer
struct Foo<each T> {
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed that flag and placed struct Foo<each T>
in test/Constraint/variadic_generic_types.swift
now.
lib/Sema/CSDiagnostics.cpp
Outdated
if (const auto choice = getOverloadChoiceIfAvailable(calleeLocator)) { | ||
if (auto *decl = choice->choice.getDeclOrNull()) { | ||
const auto applyArgToParamElt = | ||
(path.end() - 2)->getAs<LocatorPathElt::ApplyArgToParam>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think the last thing here - let's use findFirst<LocatorPathElt::ApplyArgToParam>()
and check that for presence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated. Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, thank you very much!
@swift-ci please test |
@swift-ci please test Windows platform |
@li3zhen1 Thank you for seeing this through. The next time you wish to take an issue with relatively recent prior activity, please ask the previous volunteers if they are still working on it and mind you taking over. This is not to say someone was actually still working on this particular issue, just something for newcomers to consider — we don’t want good first issues to be a competitive game. Unless otherwise agreed upon by several volunteers 🙂. |
Sorry. I’ll keep this in mind. |
@xedin Did you mean that for this case there should be no |
Removed
TVO_CanBindToHole
flag inConstraintSystem::openGenericParameter
when generic parameter is parameter pack, so that passingnil
to generic parameter pack produces fix correctly.Resolves #69432 .
Before
After
Diagnostic messages: