X Tutup
The Wayback Machine - https://web.archive.org/web/20201112022035/https://github.com/PowerShell/PowerShell/pull/13162
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

Allow explicitly specified named parameter to supersede the same one from hashtable splatting #13162

Merged
merged 10 commits into from Aug 5, 2020

Conversation

@daxian-dbw
Copy link
Member

@daxian-dbw daxian-dbw commented Jul 12, 2020

PR Summary

Fix #13114.

Allow explicitly specified named parameter to supersede the same one from hashtable splatting.
The work is done in parameter binder, so that parameters can be resolved to cover a parameter's official name, alias name, and unambiguous partial prefix name.

The changes covers covers Hashtable splatting in 3 scenarios:

  1. Cmdlet or advanced script invocation;
  2. Simple function invocation;
  3. ScriptBlock.GetPowerShell(...), where the script block contains command invocation only and uses Hashtable splatting.

Some code refactoring is done to ParameterBinderController to avoid redundant code being duplicated in CmdletParameterBinderController and ScriptParameterBinderController.

Breaking Change

This change introduces a minor breaking change to how Hashtable splatting works with simple functions when it contains key/value pairs that not a named parameters of the function. (no breaking change to cmdlet/advanced function)

With this change, the named parameters from Hashtable splatting will be moved to the end of the parameter/argument list, so as to late bind them after all explicitly specified named parameters are bound. Parameter binding for simple functions won't throw error when a specified named parameter cannot be found, but stuff the unknown named parameters to the $args of the simple function. Since the named parameters from Hashtable splatting are moved to the end of the argument list, the order it appears in args will be different, also, other explicitly specified parameters for a simple function gets bound earlier.

For example:

function SimpleTest {
    param(
        $Name,
        $Path
    )
    "Name: $Name; Path: $Path; Args: $args"
}

$hash = @{ Name = "Hello"; Blah = "World" }
SimpleTest @hash "MyPath"

Current Behavior

## 'MyPath' is not bound to `-Path` because it's the third argument in the argument list.
## So it ends up being stuffed into '$args' along with `Blah = "World"`

PS> SimpleTest @hash "MyPath"
Name: Hello; Path: ; Args: -Blah: World MyPath

New Behavior

## The arguments from @hash are moved to the end of the argument list,
## so `MyPath` becomes the first argument in the list and thus bound to '-Path'

PS> SimpleTest @hash "MyPath"
Name: Hello; Path: MyPath; Args: -Blah: World

I think the breaking change is in Bucket 3 Unlikely Grey Area because:

  1. the current behavior is confusing because how the user explicitly specified arguments after the splatted Hashtable depends on the order of them in the argument list after expanding the Hashtable.
  2. it should be rare for user to call a simple function with Hashtable splatting which contains keys that are not parameter names of the function.
  3. even if (2) happens, say the Hashtable is a config with all possible parameters and an user sends the config to functions, it's very unlikely for $args to be used in a real scenario like this.

PR Checklist

@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Jul 13, 2020

it should be rare for user to call a simple function with Hashtable splatting which contains keys that are not parameter names of the function.

I have a concern about this statement. What if such Hashtable is a config with all possible parameters and an user sends the config to functions?

@daxian-dbw
Copy link
Member Author

@daxian-dbw daxian-dbw commented Jul 13, 2020

I have a concern about this statement. What if such Hashtable is a config with all possible parameters and an user sends the config to functions?

For cmdlet/advanced functions, this will fail.
For simple functions, then the user must be ignoring $args in those functions. It's unlikely they are depending on $args in this case at all.

@JamesWTruher
Copy link
Member

@JamesWTruher JamesWTruher commented Jul 15, 2020

@BrucePay would love your opinion on this behavior

@bpayette
Copy link
Contributor

@bpayette bpayette commented Jul 15, 2020

@daxian-dbw you said: "so MyPath becomes the second argument in the list and thus bound". But that's wrong - the splatted value doesn't occupy a parameter position so MyPath should be the first argument.

@daxian-dbw
Copy link
Member Author

@daxian-dbw daxian-dbw commented Jul 15, 2020

@bpayette Right, I just corrected that comment. Thanks!

@SteveL-MSFT
Copy link
Member

@SteveL-MSFT SteveL-MSFT commented Jul 16, 2020

@PowerShell/powershell-committee discussed this and would like more community feedback. The disagreement was on whether the splatted hashtable order matters. In one case, the order dictates who overrides:

$hash = @{ Name = 'hello' }
test-function @hash -Name World
test-function -Name World @hash

If the ordering matters, then in the first case you get Name = World and the second case results in Name = hello. However, if order does not affect parameter binding then in both cases Name = World.

The argument for ordering is that it's been a useful construct in bash where latter parameters win so you can use it to override parameters declared in aliases.

The argument against is that ordering (ignoring positional parameters) has never mattered in PowerShell so this is introducing a new concept and may confuse users.

@vexx32
Copy link
Collaborator

@vexx32 vexx32 commented Jul 16, 2020

Ordering shouldn't matter. As @daxian-dbw it simply increases fragility and massively decreases readability if order matters for this.

With the knowledge that order doesn't matter, I can confidently state that Some-Command -ThisParam $value @otherParams will definitely have the expected value for -ThisParam without ever looking at the $otherParams hashtable. That's powerful.

If order mattered, you would have to always keep track of if the parameter is before or after, and if it's before the hashtable you then have to look up whether the param you're worried about is in the hashtable. Way less obvious what's going on.

Also, the hashtable is much more likely to be the reused element there, not the individual command call statement. I think it stands to reason that the more deliberate statement (assuming reuse of hashtable means that the params it contains are probably more general than just the one command call) with the lesser scope of effect should take precedence.

@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Jul 16, 2020

Ordering shouldn't matter.

I agree. It seems it is not PowerShell native behavior to depend on order of named parameters.

@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Jul 16, 2020

Also if we consider -Force switch I think users would expect the priority of this parameter regardless of its location.

@AspenForester
Copy link

@AspenForester AspenForester commented Jul 16, 2020

It seems to me that this behavior of overriding a splat is a natural follow-on from $PSDefaultParameterValues that can be overridden by providing parameters... Like @SteveL-MSFT just said as I was about to click on the Comment button.

@markdomansky
Copy link

@markdomansky markdomansky commented Jul 16, 2020

The argument for ordering is that it's been a useful construct in bash where latter parameters win so you can use it to override parameters declared in aliases.

This argument would extend that parameter behavior should be this way in all cases; so Test -Name A -Name B would not error and would output B. It doesn't seem that's intended, desired, or included in this change. Also, aliases in PS don't support parameters so people aren't leveraging that ability today. I would not support this. I want the error.

The explicit parameter should override anything splatted. It's clearer, and more natural.

As a personal preference, I generally splat at the end. Parameters and variables get long, so hitting the @ means I'm done with the line and know what's happening. With order-matters, I (and presumably others) would have to alter their coding practice to leverage this. That's the breaking change with order-matters. If you splat first today, there's no change in behavior/code; if you splat late, it does.

@rkeithhill
Copy link
Contributor

@rkeithhill rkeithhill commented Jul 19, 2020

It seems it is not PowerShell native behavior to depend on order of named parameters.

Agreed.

@SteveL-MSFT
Copy link
Member

@SteveL-MSFT SteveL-MSFT commented Jul 22, 2020

@PowerShell/powershell-committee reviewed this, we agree that within PowerShell ordering has not mattered, so we should continue that. On the breaking change for SimpleFunctions, parameters have always been implicitly positional, so the new change is accepted.

@msftbot
Copy link

@msftbot msftbot bot commented Jul 31, 2020

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

Copy link
Contributor

@PaulHigin PaulHigin left a comment

LGTM

/// and then bind the argument to the parameter.
/// </summary>
/// <param name="parameterSets">
/// The parameter set used to bind the arguments.
/// </param>
/// <param name="arguments">
/// The arguments that should be attempted to bind to the parameters of the specified
/// parameter binder.
/// </param>
/// <exception cref="ParameterBindingException">
/// if multiple parameters are found matching the name.
/// or
/// if no match could be found.
/// or
/// If argument transformation fails.
/// or
/// The argument could not be coerced to the appropriate type for the parameter.
/// or
/// The parameter argument transformation, prerequisite, or validation failed.
/// or
/// If the binding to the parameter fails.
/// </exception>
private Collection<CommandParameterInternal> BindParameters(uint parameterSets, Collection<CommandParameterInternal> arguments)
protected override void BindNamedParameter(
Comment on lines +1062 to +1064

This comment has been minimized.

@iSazonov

iSazonov Aug 5, 2020
Collaborator

(As side note, there is a full mess with 'bind the argument' vs 'bind the parameter' in comments and method names.)

This comment has been minimized.

@daxian-dbw

daxian-dbw Aug 5, 2020
Author Member

When talking about parameter binding, it should be bind an argument to a parameter, an argument is bound (to a parameter), a parameter is bound (by an argument).

@iSazonov iSazonov merged commit 63cf0c3 into PowerShell:master Aug 5, 2020
32 of 33 checks passed
32 of 33 checks passed
Codacy/PR Quality Review Not up to standards. This pull request quality could be better.
Details
CodeFactor 117 issues fixed. 12 issues found.
Details
PowerShell-CI-SSH #PR-13162-20200804.02 succeeded
Details
PowerShell-CI-linux Build #PR-13162-20200804.02 succeeded
Details
PowerShell-CI-linux (Build for Linux linux Build) Build for Linux linux Build succeeded
Details
PowerShell-CI-linux (CodeCoverage and Test Packages CodeCoverage and Test Packages) CodeCoverage and Test Packages CodeCoverage and Test Packages succeeded
Details
PowerShell-CI-linux (Test for Linux Linux Test - ElevatedPesterTests - CI) Test for Linux Linux Test - ElevatedPesterTests - CI succeeded
Details
PowerShell-CI-linux (Test for Linux Linux Test - ElevatedPesterTests - Others) Test for Linux Linux Test - ElevatedPesterTests - Others succeeded
Details
PowerShell-CI-linux (Test for Linux Linux Test - UnelevatedPesterTests - CI) Test for Linux Linux Test - UnelevatedPesterTests - CI succeeded
Details
PowerShell-CI-linux (Test for Linux Linux Test - UnelevatedPesterTests - Others) Test for Linux Linux Test - UnelevatedPesterTests - Others succeeded
Details
PowerShell-CI-linux (Test for Linux Verify xUnit Results) Test for Linux Verify xUnit Results succeeded
Details
PowerShell-CI-macos Build #PR-13162-20200804.02 had test failures
Details
PowerShell-CI-macos (Build for macOS macOS Build) Build for macOS macOS Build succeeded
Details
PowerShell-CI-macos (CodeCoverage and Test Packages CodeCoverage and Test Packages) CodeCoverage and Test Packages CodeCoverage and Test Packages succeeded
Details
PowerShell-CI-macos (Test for macOS Verify xUnit Results) Test for macOS Verify xUnit Results succeeded
Details
PowerShell-CI-macos (Test for macOS mac Test - ElevatedPesterTests - CI) Test for macOS mac Test - ElevatedPesterTests - CI succeeded
Details
PowerShell-CI-macos (Test for macOS mac Test - ElevatedPesterTests - Others) Test for macOS mac Test - ElevatedPesterTests - Others succeeded
Details
PowerShell-CI-macos (Test for macOS mac Test - UnelevatedPesterTests - CI) Test for macOS mac Test - UnelevatedPesterTests - CI succeeded
Details
PowerShell-CI-macos (Test for macOS mac Test - UnelevatedPesterTests - Others) Test for macOS mac Test - UnelevatedPesterTests - Others succeeded
Details
PowerShell-CI-static-analysis Build #PR-13162-20200804.02 succeeded
Details
PowerShell-CI-static-analysis (Markdown and Common Tests) Markdown and Common Tests succeeded
Details
PowerShell-CI-static-analysis (Secret Scan) Secret Scan succeeded
Details
PowerShell-CI-windows Build #PR-13162-20200804.02 succeeded
Details
PowerShell-CI-windows (Build for Windows Windows Build) Build for Windows Windows Build succeeded
Details
PowerShell-CI-windows (Packaging for Windows Windows Packaging) Packaging for Windows Windows Packaging succeeded
Details
PowerShell-CI-windows (Test for Windows Verify xUnit Results) Test for Windows Verify xUnit Results succeeded
Details
PowerShell-CI-windows (Test for Windows Windows Test - ElevatedPesterTests - CI) Test for Windows Windows Test - ElevatedPesterTests - CI succeeded
Details
PowerShell-CI-windows (Test for Windows Windows Test - ElevatedPesterTests - Others) Test for Windows Windows Test - ElevatedPesterTests - Others succeeded
Details
PowerShell-CI-windows (Test for Windows Windows Test - UnelevatedPesterTests - CI) Test for Windows Windows Test - UnelevatedPesterTests - CI succeeded
Details
PowerShell-CI-windows (Test for Windows Windows Test - UnelevatedPesterTests - Others) Test for Windows Windows Test - UnelevatedPesterTests - Others succeeded
Details
SSH-Tests Build #PR-13162-20200804.02 succeeded
Details
WIP Ready for review
Details
license/cla All CLA requirements met.
Details
@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Aug 5, 2020

Great work!

@RipTornMist
Copy link

@RipTornMist RipTornMist commented Aug 5, 2020

Nice Work!

@daxian-dbw daxian-dbw deleted the daxian-dbw:splatting branch Aug 5, 2020
xtqqczze added a commit to xtqqczze/PowerShell that referenced this pull request Aug 8, 2020
…from hashtable splatting (PowerShell#13162)

Allow explicitly specified named parameter to supersede the same one from hashtable splatting.
The work is done in parameter binder, so that parameters can be resolved to cover a parameter's official name, alias name, and unambiguous partial prefix name.

The changes covers covers Hashtable splatting in 3 scenarios:

- Cmdlet or advanced script invocation;
- Simple function invocation;
- ScriptBlock.GetPowerShell(...), where the script block contains command invocation only and uses Hashtable splatting.

Some code refactoring is done to ParameterBinderController to avoid redundant code being duplicated in CmdletParameterBinderController and ScriptParameterBinderController.
@sdwheeler
Copy link
Collaborator

@sdwheeler sdwheeler commented Aug 10, 2020

@daxian-dbw Should this be assigned to a milestone?

@daxian-dbw daxian-dbw added this to the 7.1.0-preview.6 milestone Aug 10, 2020
@msftbot
Copy link

@msftbot msftbot bot commented Aug 17, 2020

🎉v7.1.0-preview.6 has been released which incorporates this pull request.🎉

Handy links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
X Tutup