diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 9965824a927..4a0cd1e983d 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1595,7 +1595,6 @@ public abstract class CachedValidValuesGeneratorBase : IValidateSetValuesGenerat // Cached valid values. private string[] _validValues; private readonly int _validValuesCacheExpiration; - /// /// Initializes a new instance of the class. /// @@ -1655,7 +1654,7 @@ public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute // of 'validValuesGenerator'. private readonly string[] _validValues; private readonly IValidateSetValuesGenerator validValuesGenerator = null; - + private readonly ScriptBlock validValuesScript; // The valid values generator cache works across 'ValidateSetAttribute' instances. private static readonly ConcurrentDictionary s_ValidValuesGeneratorCache = new ConcurrentDictionary(); @@ -1686,11 +1685,36 @@ public IList ValidValues { get { - if (validValuesGenerator == null) + if (validValuesGenerator == null && validValuesScript == null) { return _validValues; } + if (validValuesScript is not null) + { + var validValuesFromScript = new List(); + var customResults = validValuesScript.Invoke(); + if (customResults == null || customResults.Count == 0) + { + validValuesFromScript = null; + } + + foreach (var customResult in customResults) + { + var resultAsString = LanguagePrimitives.ConvertTo(customResult); + if (resultAsString != null) + { + validValuesFromScript.Add(resultAsString); + continue; + } + + var resultToString = customResult.ToString(); + validValuesFromScript.Add(resultToString); + } + + return validValuesFromScript; + } + var validValuesLocal = validValuesGenerator.GetValidValues(); if (validValuesLocal == null) @@ -1790,6 +1814,16 @@ public ValidateSetAttribute(Type valuesGeneratorType) validValuesGenerator = s_ValidValuesGeneratorCache.GetOrAdd( valuesGeneratorType, static (key) => (IValidateSetValuesGenerator)Activator.CreateInstance(key)); } + + /// + /// Initializes a new instance of the class. + /// + /// ScriptBlock that generates list of valid values + /// For null arguments. + public ValidateSetAttribute(ScriptBlock scriptBlock) + { + validValuesScript = scriptBlock; + } } /// diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 68bb523427b..cf768b9c68b 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -1645,6 +1645,10 @@ private static Attribute NewValidateSetAttribute(AttributeAst ast) typeof(System.Management.Automation.IValidateSetValuesGenerator).FullName); } } + else if (ast.PositionalArguments.Count == 1 && ast.PositionalArguments[0] is ScriptBlockExpressionAst scriptBlockAst) + { + result = new ValidateSetAttribute(scriptBlockAst.ScriptBlock.GetScriptBlock()); + } else { // 'ValidateSet("value1","value2", IgnoreCase=$false)' is supported in scripts. diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index f984e3b72c0..4521f458ef0 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1096,6 +1096,15 @@ switch ($x) $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' } + It "Tab completion for validateSet attribute with ScriptBlock" { + function foo { param([ValidateSet({'cat','dog'})]$p) } + $inputStr = "foo " + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'cat' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' + } + It "Tab completion for validateSet attribute takes precedence over enums" { function foo { param([ValidateSet('DarkBlue','DarkCyan')][ConsoleColor]$p) } $inputStr = "foo "