From c4fb1f8315bf5bd754ef75d8d871054a98c071e6 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Wed, 17 Nov 2021 14:33:26 -0500 Subject: [PATCH] Flow declared type information to function arguments (#5188) * Flow declared type information to function arguments * Fix up tests --- .../Utils/BuiltInTestTypes.cs | 2 + .../TypeSystem/DeclaredTypeManager.cs | 54 +++++++++++--- .../CompletionTests.cs | 74 ++++++++++++++++++- 3 files changed, 117 insertions(+), 13 deletions(-) diff --git a/src/Bicep.Core.UnitTests/Utils/BuiltInTestTypes.cs b/src/Bicep.Core.UnitTests/Utils/BuiltInTestTypes.cs index 74f1321cf45..ee09545bef7 100644 --- a/src/Bicep.Core.UnitTests/Utils/BuiltInTestTypes.cs +++ b/src/Bicep.Core.UnitTests/Utils/BuiltInTestTypes.cs @@ -184,6 +184,8 @@ private static ResourceTypeComponents ListFunctionsType() var withInputInput = new ObjectType("WithInputInput", TypeSymbolValidationFlags.Default, new [] { new TypeProperty("withInputInputVal", LanguageConstants.String, TypePropertyFlags.WriteOnly | TypePropertyFlags.Required, "Foo description"), + new TypeProperty("optionalVal", LanguageConstants.String, TypePropertyFlags.WriteOnly, "optionalVal description"), + new TypeProperty("optionalLiteralVal", TypeHelper.CreateTypeUnion(new StringLiteralType("either"), new StringLiteralType("or")), TypePropertyFlags.WriteOnly, "optionalLiteralVal description"), }, null); var withInputOutput = new ObjectType("WithInputOutput", TypeSymbolValidationFlags.Default, new [] { diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 0b42d43ebf0..58cf51b5d29 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -101,6 +101,9 @@ public DeclaredTypeManager(TypeManager typeManager, IBinder binder) case StringSyntax @string: return GetStringType(@string); + + case FunctionArgumentSyntax functionArgument: + return GetFunctionArgumentType(functionArgument); } return null; @@ -272,14 +275,17 @@ private DeclaredTypeAssignment GetModuleType(ModuleDeclarationSyntax syntax) // we are only handling paths in the AST that are going to produce a declared type // arrays can exist under a variable declaration, but variables don't have declared types, // so we don't need to check that case - if (parent is ObjectPropertySyntax) + switch (parent) { - // this array is a value of the property - // the declared type should be the same as the array and we should propagate the flags - return GetDeclaredTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax); + case ObjectPropertySyntax: + // this array is a value of the property + // the declared type should be the same as the array and we should propagate the flags + return GetDeclaredTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax); + case FunctionArgumentSyntax: + return GetDeclaredTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax); + default: + return null; } - - return null; } private DeclaredTypeAssignment? GetStringType(StringSyntax syntax) @@ -289,14 +295,33 @@ private DeclaredTypeAssignment GetModuleType(ModuleDeclarationSyntax syntax) // we are only handling paths in the AST that are going to produce a declared type // strings can exist under a variable declaration, but variables don't have declared types, // so we don't need to check that case - if (parent is ObjectPropertySyntax || parent is ArrayItemSyntax) + switch (parent) { - // this string is a value of the property - // the declared type should be the same as the string and we should propagate the flags - return GetDeclaredTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax); + case ObjectPropertySyntax: + case ArrayItemSyntax: + // this string is a value of the property + // the declared type should be the same as the string and we should propagate the flags + return GetDeclaredTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax); + case FunctionArgumentSyntax: + return GetDeclaredTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax); + default: + return null; } + } - return null; + private DeclaredTypeAssignment? GetFunctionArgumentType(FunctionArgumentSyntax syntax) + { + var parent = this.binder.GetParent(syntax); + if (parent is not FunctionCallSyntaxBase parentFunction || + SymbolHelper.TryGetSymbolInfo(this.binder, this.GetDeclaredType, parent) is not FunctionSymbol functionSymbol) + { + return null; + } + + var argIndex = parentFunction.Arguments.IndexOf(syntax); + var declaredType = functionSymbol.GetDeclaredArgumentType(argIndex); + + return new DeclaredTypeAssignment(declaredType, declaringSyntax: null); } private DeclaredTypeAssignment? GetArrayItemType(ArrayItemSyntax syntax) @@ -515,6 +540,13 @@ private DeclaredTypeAssignment GetModuleType(ModuleDeclarationSyntax syntax) // the object is an item in an array // use the item's type and propagate flags return TryCreateAssignment(ResolveDiscriminatedObjects(namespaceType.ConfigurationType.Type, syntax), syntax, importAssignment.Flags); + case FunctionArgumentSyntax: + if (GetDeclaredTypeAssignment(parent) is not {} parentAssignment) + { + return null; + } + + return TryCreateAssignment(ResolveDiscriminatedObjects(parentAssignment.Reference.Type, syntax), syntax, parentAssignment.Flags); } return null; diff --git a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs index 24e88131fbf..44950f3f0cd 100644 --- a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs @@ -1307,7 +1307,7 @@ public async Task List_functions_accepting_inputs_suggest_api_version_param() name: 'abc' } -var outTest = abc.listWithInput('2020-01-01') +var outTest = abc.listWithInput('2020-01-01'|) "); } @@ -1341,6 +1341,75 @@ public async Task List_functions_accepting_inputs_suggest_required_properties() "); } + [TestMethod] + public async Task List_functions_accepting_inputs_permit_object_key_completions() + { + var fileWithCursors = @" +resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = { + name: 'abc' +} + +var outTest = abc.listWithInput('2020-01-01', { + withInputInputVal: 'hello' + | +}) +"; + + var (file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors); + var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///main.bicep"), file); + using var helper = await LanguageServerHelper.StartServerWithTextAsync(TestContext, file, bicepFile.FileUri, creationOptions: new LanguageServer.Server.CreationOptions(NamespaceProvider: BuiltInTestTypes.Create())); + var client = helper.Client; + var completions = await RequestCompletion(client, bicepFile, cursors.Single()); + completions.Should().Contain(x => x.Label == "optionalVal"); + + var updatedFile = ApplyCompletion(bicepFile, completions.Single(x => x.Label == "optionalVal")); + updatedFile.Should().HaveSourceText(@" +resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = { + name: 'abc' +} + +var outTest = abc.listWithInput('2020-01-01', { + withInputInputVal: 'hello' + optionalVal:| +}) +"); + } + + [TestMethod] + public async Task List_functions_accepting_inputs_permit_object_value_completions() + { + var fileWithCursors = @" +resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = { + name: 'abc' +} + +var outTest = abc.listWithInput('2020-01-01', { + withInputInputVal: 'hello' + optionalLiteralVal: | +}) +"; + + var (file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors); + var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///main.bicep"), file); + using var helper = await LanguageServerHelper.StartServerWithTextAsync(TestContext, file, bicepFile.FileUri, creationOptions: new LanguageServer.Server.CreationOptions(NamespaceProvider: BuiltInTestTypes.Create())); + var client = helper.Client; + var completions = await RequestCompletion(client, bicepFile, cursors.Single()); + completions.Should().Contain(x => x.Label == "'either'"); + completions.Should().Contain(x => x.Label == "'or'"); + + var updatedFile = ApplyCompletion(bicepFile, completions.Single(x => x.Label == "'either'")); + updatedFile.Should().HaveSourceText(@" +resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = { + name: 'abc' +} + +var outTest = abc.listWithInput('2020-01-01', { + withInputInputVal: 'hello' + optionalLiteralVal: 'either'| +}) +"); + } + [TestMethod] public async Task List_functions_return_property_completions() { @@ -1365,7 +1434,7 @@ public async Task List_functions_return_property_completions() name: 'abc' } -var outTest = abc.listWithInput().withInputOutputVal +var outTest = abc.listWithInput().withInputOutputVal| "); } @@ -1640,6 +1709,7 @@ private static BicepFile ApplyCompletion(BicepFile bicepFile, CompletionItem com } break; case InsertTextFormat.PlainText: + textToInsert = textToInsert + "|"; break; default: throw new InvalidOperationException();