Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add support for ITuple and Deconstruct assignment to multiple variables#26617

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
yotsuda wants to merge5 commits intoPowerShell:master
base:master
Choose a base branch
Loading
fromyotsuda:fix-issue-7471-ituple-array-assignment

Conversation

@yotsuda
Copy link
Contributor

PR Summary

Adds support for assigningITuple types (System.Tuple,System.ValueTuple) and types withDeconstruct methods (DictionaryEntry,KeyValuePair<,>, etc.) to multiple variables using array assignment syntax.

Fixes#7471

PR Context

Problem

When assigning aTuple,ValueTuple,DictionaryEntry, orKeyValuePair to multiple variables, PowerShell previously assigned the entire object to the first variable instead of deconstructing it:

$tuple= [Tuple]::Create(1,2)$a,$b=$tuple# Result: $a contains the entire tuple, $b is $null$de= [System.Collections.DictionaryEntry]::new("key","value")$k,$v=$de# Result: $k contains the entire DictionaryEntry, $v is $null

This prevented convenient use of APIs that return tuples (like[Math]::DivRem()) and made hashtable enumeration cumbersome.

Solution

Added special handling inPSGetMemberBinder for two cases:

  1. ITuple types - Detected viaSystem.Runtime.CompilerServices.ITuple interface, elements extracted using the indexer
  2. Deconstruct methods - Types with a publicDeconstruct method with allout parameters (likeDictionaryEntry andKeyValuePair<,>)

Both handle three scenarios: exact match, fewer elements than variables (fills with null), more elements than variables (remaining go to last variable as array).

PR Checklist

Changes Made

1.Compiler.cs (+18 lines)

  • Added cached reflection references forITuple.Length property andITuple.get_Item method
  • Added references forEnumerableOps.GetTupleSlice,GetDeconstructMethod,InvokeDeconstruct, andGetDeconstructSlice methods

2.Binders.cs (+120 lines)

  • AddedITuple detection and handling inPSGetMemberBinder.FallbackGetMember
  • AddedDeconstruct method detection and handling
  • Implemented three-case logic for both: exact element count, fewer elements, more elements

3.MiscOps.cs (+97 lines)

  • AddedGetTupleSlice - extracts remaining elements from ITuple
  • AddedGetDeconstructMethod - finds Deconstruct method on a type
  • AddedInvokeDeconstruct - invokes Deconstruct and returns values
  • AddedGetDeconstructSlice - extracts remaining values from already-deconstructed array (avoids calling Deconstruct multiple times)

4.Indexer.Tests.ps1 (+136 lines)

  • Added 13 comprehensive tests covering ITuple and Deconstruct scenarios

Total: 4 files changed, 371 insertions(+)

Behavior Examples

Tuple assignment

$tuple= [Tuple]::Create(1,2)$a,$b=$tuple
$a$b
Before(1, 2) tuple object$null
After12

DictionaryEntry assignment

$de= [System.Collections.DictionaryEntry]::new("key","value")$k,$v=$de
$k$v
BeforeDictionaryEntry object$null
After"key""value"

KeyValuePair assignment

$kvp= [System.Collections.Generic.KeyValuePair[string,int]]::new("count",42)$k,$v=$kvp
$k$v
BeforeKeyValuePair object$null
After"count"42

Hashtable enumeration

$ht=@{name="test" }foreach ($entryin$ht.GetEnumerator()) {$key,$value=$entry}
$key$value
BeforeDictionaryEntry object$null
After"name""test"

Math.DivRem

$quotient,$remainder= [Math]::DivRem(17,5)
$quotient$remainder
Before(3, 2) ValueTuple object$null
After32

Testing

Test Cases (13 new tests)

ITuple tests:

  1. Tuple with exact element count (2 elements)
  2. Single element Tuple (1 element) - boundary
  3. Tuple with 7 elements - boundary (max without nesting)
  4. Tuple with 8 elements - boundary (uses internal Rest nesting)
  5. ValueTuple with 3 elements
  6. More elements than variables (overflow to last)
  7. Fewer elements than variables (null for extras)
  8. Math.DivRem practical example

Deconstruct tests:
9. DictionaryEntry assignment
10. KeyValuePair assignment
11. DictionaryEntry with more variables than elements
12. Hashtable enumeration practical example
13. Deconstruct method called only once (verifies no redundant calls)

Test Results

EnvironmentPassedFailedStatus
Local build20/200✅ All pass

Implementation Details

Supported Types

Via ITuple interface:

  • System.Tuple<> (1-8 elements)
  • System.ValueTuple<> (1-8 elements)
  • Any type implementingSystem.Runtime.CompilerServices.ITuple

Via Deconstruct method:

  • System.Collections.DictionaryEntry
  • System.Collections.Generic.KeyValuePair<,>
  • Any type with a publicDeconstruct(out T1, out T2, ...) method

Design Decisions

  1. ITuple first, then Deconstruct - ITuple is checked first as it's more specific; Deconstruct is a fallback for types like DictionaryEntry
  2. Length-based restriction for ITuple - Dynamic binding includes tuple length in restrictions for correct caching
  3. Instance Deconstruct only - Extension methods are not supported (consistent with PowerShell's general behavior)
  4. Consistent overflow/underflow behavior - Follows same patterns as array/IList assignment
  5. Single Deconstruct invocation - GetDeconstructSlice takes the already-deconstructed array to avoid calling Deconstruct() multiple times

@iSazonoviSazonov added the CL-EngineIndicates that a PR should be marked as an engine change in the Change Log labelDec 15, 2025
@iSazonoviSazonov requested review fromCopilot and removed request forCopilotDecember 15, 2025 12:47
Copy link
Contributor

CopilotAI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds tuple deconstruction support to PowerShell, enablingITuple types (Tuple, ValueTuple) and types withDeconstruct methods (DictionaryEntry, KeyValuePair<,>) to be assigned to multiple variables using array assignment syntax. This addresses issue#7471 by implementing special handling in the dynamic binding layer to automatically decompose these types into their constituent elements during assignment operations.

Key changes:

  • EnhancedPSGetMemberBinder to detect and handle ITuple interface and Deconstruct methods
  • Added helper methods inEnumerableOps for slicing tuples and invoking deconstruction
  • Comprehensive test coverage with 13 new tests for various tuple and deconstruction scenarios

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

FileDescription
test/powershell/Language/Scripting/Indexer.Tests.ps1Added 13 comprehensive tests covering ITuple and Deconstruct assignment scenarios including edge cases (underflow, overflow, single Deconstruct call verification)
src/System.Management.Automation/engine/runtime/Operations/MiscOps.csImplemented four helper methods in EnumerableOps: GetTupleSlice, GetDeconstructMethod, InvokeDeconstruct, and GetDeconstructSlice for tuple and deconstruct operations
src/System.Management.Automation/engine/runtime/Binding/Binders.csExtended PSGetMemberBinder.FallbackGetMember with ITuple and Deconstruct detection and handling logic with proper dynamic restrictions
src/System.Management.Automation/engine/parser/Compiler.csAdded cached reflection info for ITuple properties/methods and EnumerableOps helper methods to improve runtime performance

💡Add Copilot custom instructions for smarter, more guided reviews.Learn how to get started.

…cOps.csCo-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Comment on lines 1036 to 1037
variTuple=target.ValueasSystem.Runtime.CompilerServices.ITuple;
if(iTupleis notnull)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
variTuple=target.ValueasSystem.Runtime.CompilerServices.ITuple;
if(iTupleisnotnull)
variTuple=target.ValueasSystem.Runtime.CompilerServices.ITuple;
if(target.ValueisITupleiTuple)

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Thank you for the quick feedback! Applied with correction (merged both lines into one). Commit:1d4f6a6

Copy link
Collaborator

Choose a reason for hiding this comment

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

System.Runtime.CompilerServices is still used in 5 points. Please remove.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Sorry for missing those. Removed the prefix from 4 occurrences in my changes:

  • Compiler.cs (lines 301, 304)
  • Binders.cs (line 1082)
  • MiscOps.cs (line 3651)

The 5th reference in CorePsTypeCatalog.cs is not my code. It's an auto-generated type catalog string literal. Should I update that one as well?

Commit:89b0793

iSazonov reacted with thumbs up emoji
}

It'Hashtable enumeration produces deconstructable DictionaryEntry' {
$ht=@{name="test" }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does the feature work for PSCustomObject?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

No,PSCustomObject is not supported. It doesn't implementITuple,IList, or have aDeconstruct method.

I intentionally didn't add support becausePSCustomObject properties are name-based, not position-based. Order-based deconstruction would be fragile:

$person= [PSCustomObject]@{name="John";age=30 }$name,$age=$person# Works# Later, add a property...$person= [PSCustomObject]@{id=1;name="John";age=30 }$name,$age=$person# Silently breaks: $name = 1, $age = "John"

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Name-based matching (like JavaScript'sconst { name, age } = person) would be safer, but I believe that would require a different approach and new syntax—beyond the scope of this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, it seems we have such issue for named arguments and could enhance it to the area.

yotsuda reacted with thumbs up emoji
Copy link
Collaborator

@iSazonoviSazonov left a comment

Choose a reason for hiding this comment

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

@yotsuda Although this is a great feature, I can't imagine when the owners of the code will find the time to do a review. Let's be patient.

yotsuda reacted with thumbs up emoji
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

Copilot code reviewCopilotCopilot left review comments

@iSazonoviSazonoviSazonov approved these changes

@daxian-dbwdaxian-dbwAwaiting requested review from daxian-dbwdaxian-dbw is a code owner

@SeeminglyScienceSeeminglyScienceAwaiting requested review from SeeminglyScienceSeeminglyScience is a code owner

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

CL-EngineIndicates that a PR should be marked as an engine change in the Change Log

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Assigning to an array literal of variables (LHS) should support Deconstruct methods

2 participants

@yotsuda@iSazonov

[8]ページ先頭

©2009-2025 Movatter.jp