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 "