-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[ConstraintSystem] adjust matching position of unlabeled parameter #32037
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
by considering unbounded parameter which is at left of current parameter in `matchCallArgument`. It avoids matching which has unwanted crossing pairs between argument and parameter. It provides more natural label error diagnostics.
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
@swift-ci please smoke test OS X |
Same CI failure #32042 |
@xedin Could you review this patch? |
I am hoping to get to this really soon. |
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 the idea is correct here but implementation I'm not so sure about. It seems like there might be a lot more sound algorithm hiding in a mess of matchCallArguments
, should be re-think this from ground up?
I think we might want to experiment with following algorithm:
- Try to claim all labeled arguments regardless of their position
- If argument was out of order but matched, adjust argument position to expected by parameter to make it seem like it was in the correct spot.
- If labeled argument couldn't be claimed at this step it's always marked as "extraneous"
and argument positions are adjusted to pretend that it doesn't exist.
- Remaining arguments are matched positionally in strict left-to-right order, first unclaimed argument is matched against first unclaimed parameter on the left.
WDYT?
/// func f(aa: Int, _ bb: Int) {} | ||
/// f(0, 1) | ||
/// @endcode | ||
/// Choice argument[1] for parameter[1] so that parameter[0] will be bounded |
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: bounded
-> bound
.
/// to argument[0] later. | ||
/// | ||
/// Because variadics parameter can be bounded with more than one arguments, | ||
/// they don't this. |
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.
Let's re-phrase - Avoid considering variadic parameters because such parameters could be bound to one than one argument
.
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.
Thanks!
} | ||
|
||
unsigned keepedArgCount = 0; | ||
for (unsigned ai = nextArgIdx; ai < numArgs; ai++) { |
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.
Could you please re-name ai
to be argIdx
for clarity?
if (paramLabel.empty() && paramIdx && !forVariadic && | ||
!params[*paramIdx].isVariadic()) { | ||
unsigned unboundedParamCount = 0; | ||
for (unsigned pi = 0; pi < *paramIdx; pi++) { |
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.
Could you please re-name pi
to be paramIdx
for clarity?
/// they don't this. | ||
if (paramLabel.empty() && paramIdx && !forVariadic && | ||
!params[*paramIdx].isVariadic()) { | ||
unsigned unboundedParamCount = 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.
How about numUnclaimedParams
?
@@ -719,15 +717,15 @@ func testUnlabeledParameterBindingPosition() { | |||
// expected-error@-1:17 {{extra argument 'xx' in call}} | |||
|
|||
f(xx: 91, 1, 92) |
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.
Should this be diagnosed as extra argument xx:
and a missing argument label 'aa:' at position #2
instead?
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.
Hmm... Yes.
I think that most expected result here is:
bindings:
aa: => `1`
_ bb: => `92`
errors:
- extra argument `xx: 91`
- missing label for `aa:`
} | ||
} | ||
|
||
unsigned keepedArgCount = 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.
Should this be numUnclaimedArgs
?
|
||
nextArgIdx = std::max(nextArgIdx, ai); | ||
|
||
if (keepedArgCount >= unboundedParamCount) { |
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 this is relationship between unclaimed arguments and parameters important that way?
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.
This is exactly important.
unboundedParamCount
represents how many arguments should be skipped.
A number of skipping is derived from unclaimed parameters.
keepedArgCount
is just counter to process skipping.
I explain with two examples.
example1
func f(aa: Int, _ bb: Int) {}
f(0, 1)
I use following definitions for explanation:
bindNextParameter.1
isbindNextParameter
called from line 604.bindNextParameter.2
isbindNextParameter
called from line 689.
With example1, current behavior is following.
execution:
bindNextParameter.1(paramIdx=0, nextArgIdx=0):
params[aa:] => fail
bindNextParameter.1(paramIdx=1, nextArgIdx=0):
params[_ bb:] => `0` -- (t1)
bindNextParameter.2(paramIdx=0, nextArgIdx=0):
params[aa:] => `1`
bindings:
aa: => `1`
_ bb: => `0` (crossing!)
errors:
- missing label for `aa:`
I want to shift binding target for _ bb:
from 0
to 1
at (t1).
So it needs to skip one argument which is 0
.
The number one is derived from unboundedParamCount
.
keepedArgCount
is counter which represents number of skipped arguments during skipping loop.
With example1, patched behavior is following:
execution:
bindNextParameter.1(paramIdx=0, nextArgIdx=0):
params[aa:] => fail
bindNextParameter.1(paramIdx=1, nextArgIdx=0):
adjustment:
unboundedParamCount = 1
keepedArgCount = 1
nextArgIdx = 1
params[_ bb:] => `1`
bindNextParameter.2(paramIdx=0, nextArgIdx=0):
params[aa:] => `0`
bindings:
aa: => `0`
_ bb: => `1`
errors:
- missing label for `aa:`
Labeling errors are same.
But it avoids crossed bindings.
example2
func f(aa: Int, bb: Int, _ cc: Int, dd: Int = 0) {}
f(0, 1, 2)
With example1, current behavior is following.
execution:
bindNextParameter.1(paramIdx=0, nextArgIdx=0):
params[aa:] => fail
bindNextParameter.1(paramIdx=1, nextArgIdx=0):
params[bb:] => fail
bindNextParameter.1(paramIdx=2, nextArgIdx=0):
params[_ cc:] => `0` -- (t2)
bindNextParameter.1(paramIdx=3, nextArgIdx=0):
params[dd:] => fail
bindNextParameter.2(paramIdx=0, nextArgIdx=0):
params[aa:] => `1`
bindNextParameter.2(paramIdx=1, nextArgIdx=0):
params[bb:] => `2`
bindings:
aa: => `1`
bb: => `2`
_ cc: => `0` (crossing!)
dd: => none
errors:
- missing argument label for `aa:`
- missing argument label for `bb:`
I want to shift binding target for _ cc:
from 0
to 2
at (t2).
So it needs to skip two arguments which are 0
and 1
.
The number two is derived from unboundedParamCount
.
keepedArgCount
is counter which represents number of skipped arguments during skipping loop.
With example1, patched behavior is following:
execution:
bindNextParameter.1(paramIdx=0, nextArgIdx=0):
params[aa:] => fail
bindNextParameter.1(paramIdx=1, nextArgIdx=0):
params[bb:] => fail
bindNextParameter.1(paramIdx=2, nextArgIdx=0):
adjustment:
unboundedParamCount = 2
keepedArgCount = 2
nextArgIdx = 2
params[_ cc:] => `2`
bindNextParameter.2(paramIdx=0, nextArgIdx=0):
params[aa:] => `0`
bindNextParameter.2(paramIdx=1, nextArgIdx=1):
params[bb:] => `1`
bindings:
aa: => `0`
bb: => `1`
_ cc: => `2`
dd: => none
errors:
- missing argument label for `aa:`
- missing argument label for `bb:`
In this case, labeling errors are same.
But it avoids crossed bindings.
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 the intent here but the problem is that unboundedParamCount
is logically unrelated to keepedArgCount
because unclaimed parameters could be scattered all over the call, their quantity shouldn't affect argument consideration... It's convenient that it works for some examples but I'm pretty sure there are examples made worse with these changes.
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.
@xedin
I can understand what you say that possibility of case happen bad effect.
I created new patch at #32082 and revise this idea.
New algorithm do similar thing in stage 2 what I call.
This is run inside separated group by labeled argument binding in stage 1.
So it is well structured and I don't think bad effect more.
Reconsidering whole binding algorithm in I think your draft is basically good but have one concern. With example below func f(_ aa: Int, _ bb: Int) {}
f(aa: 0, 1) it would be:
but it should be:
It can be fixed with small amending.
I will make new patch in this way. |
Right, I wonder if it would be possible to get access to internal labels while doing this matching... This way it would be much easier to diagnose certain cases where internal labels line up... |
Does internal label mean parameter name inside function? I did not distinguish between the two below.
By the way, I succeeded to create new algorithm which can solve all cases we discussed so far at #32082 . I now start to write explanation which will be long. |
Yes, I meant the label which is used inside of function. |
Summary
This patch adjust matching position of unlabeled parameter
by considering unbounded parameter which is at left of current
parameter in
matchCallArgument
.It avoids matching which has unwanted crossing pairs between
argument and parameter.
It provides more natural label error diagnostics.
Detail
This patch changes
parameterBindings
built inmatchCallArgument
.Following is dumped value of
parameterBindings
.Please also check change of diagnostics in diff of this patch.
It provides more natural result.
Future plan and relation of this patch
This patch is split from my another project.
#30444
I have plan to make next patch that change
LabelingFailure
to use bindings built inmatchCallArgument
.It will improve diagnostics like following cases:
This patch is needed before this future plan.
See below:
Current diagnostics is good for this case.
But binding based diagnostics will change it bad.
Because current bindings is
param[0] = 1, param[1] = 0
as I wrote above.This patch changes bindings here to
param[0] = 0, param[1] = 1
.So this future plan will be good with this patch.