diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index ac8b404835a..afe2ae29d0e 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -5587,16 +5587,40 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a return AstVisitAction.StopVisit; } - if (assignmentStatementAst.Left is ConvertExpressionAst convertExpression) + if (assignmentStatementAst.Left is AttributedExpressionAst attributedExpression) { - if (convertExpression.Child is VariableExpressionAst variableExpression) + var firstConvertExpression = attributedExpression as ConvertExpressionAst; + ExpressionAst child = attributedExpression.Child; + while (child is AttributedExpressionAst attributeChild) + { + if (firstConvertExpression is null && attributeChild is ConvertExpressionAst convertExpression) + { + // Multiple type constraint can be set on a variable like this: [int] [string] $Var1 = 1 + // But it's the left most type constraint that determines the final type. + firstConvertExpression = convertExpression; + } + + child = attributeChild.Child; + } + + if (child is VariableExpressionAst variableExpression) { if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) { return AstVisitAction.Continue; } - SaveVariableInfo(variableExpression.VariablePath.UserPath, convertExpression.StaticType, isConstraint: true); + if (firstConvertExpression is not null) + { + SaveVariableInfo(variableExpression.VariablePath.UserPath, firstConvertExpression.StaticType, isConstraint: true); + } + else + { + Type lastAssignedType = assignmentStatementAst.Right is CommandExpressionAst commandExpression + ? GetInferredVarTypeFromAst(commandExpression.Expression) + : null; + SaveVariableInfo(variableExpression.VariablePath.UserPath, lastAssignedType, isConstraint: false); + } } } else if (assignmentStatementAst.Left is VariableExpressionAst variableExpression) diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 5bb67a3ab2b..7745ef37d22 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -84,6 +84,17 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].CompletionText | Should -BeExactly '$CurrentItem' } + It 'Should complete variables set with an attribute' { + $res = TabExpansion2 -inputScript '[ValidateNotNull()]$Var1 = 1; $Var' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$Var1' + } + + It 'Should use the first type constraint in a variable assignment in the tooltip' { + $res = TabExpansion2 -inputScript '[int] [string] $Var1 = 1; $Var' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$Var1' + $res.CompletionMatches[0].ToolTip | Should -BeExactly '[int]$Var1' + } + It 'Should not complete parameter name' { $res = TabExpansion2 -inputScript 'param($P' $res.CompletionMatches.Count | Should -Be 0