From c0aac54f30c48c18d65fee9f35fd918e1ea11375 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Thu, 2 Nov 2023 10:09:15 -0400 Subject: [PATCH 01/20] Rename '<' and '>' tokens to LeftChevron and RightChevron, respectively --- src/Bicep.Core.UnitTests/TypeSystem/OperatorTests.cs | 4 ++-- src/Bicep.Core/Parsing/BaseParser.cs | 4 ++-- src/Bicep.Core/Parsing/Lexer.cs | 4 ++-- src/Bicep.Core/Parsing/TokenType.cs | 4 ++-- src/Bicep.Core/Syntax/Operators.cs | 5 ++--- src/Bicep.Core/Syntax/SyntaxFactory.cs | 4 ++-- src/Bicep.Core/Syntax/SyntaxFacts.cs | 4 ++-- src/Bicep.Decompiler/BicepHelpers/SyntaxHelpers.cs | 4 ++-- 8 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Bicep.Core.UnitTests/TypeSystem/OperatorTests.cs b/src/Bicep.Core.UnitTests/TypeSystem/OperatorTests.cs index a1eca5f7cbb..bacc3065a67 100644 --- a/src/Bicep.Core.UnitTests/TypeSystem/OperatorTests.cs +++ b/src/Bicep.Core.UnitTests/TypeSystem/OperatorTests.cs @@ -132,9 +132,9 @@ public void Binary_operator_resolves_correct_type(BinaryOperator @operator, Type BinaryOperator.NotEquals => TestSyntaxFactory.CreateToken(TokenType.NotEquals), BinaryOperator.EqualsInsensitive => TestSyntaxFactory.CreateToken(TokenType.EqualsInsensitive), BinaryOperator.NotEqualsInsensitive => TestSyntaxFactory.CreateToken(TokenType.NotEqualsInsensitive), - BinaryOperator.LessThan => TestSyntaxFactory.CreateToken(TokenType.LessThan), + BinaryOperator.LessThan => TestSyntaxFactory.CreateToken(TokenType.LeftChevron), BinaryOperator.LessThanOrEqual => TestSyntaxFactory.CreateToken(TokenType.LessThanOrEqual), - BinaryOperator.GreaterThan => TestSyntaxFactory.CreateToken(TokenType.GreaterThan), + BinaryOperator.GreaterThan => TestSyntaxFactory.CreateToken(TokenType.RightChevron), BinaryOperator.GreaterThanOrEqual => TestSyntaxFactory.CreateToken(TokenType.GreaterThanOrEqual), BinaryOperator.Add => TestSyntaxFactory.CreateToken(TokenType.Plus), BinaryOperator.Subtract => TestSyntaxFactory.CreateToken(TokenType.Minus), diff --git a/src/Bicep.Core/Parsing/BaseParser.cs b/src/Bicep.Core/Parsing/BaseParser.cs index 6b64108a770..baf9ccf3a3b 100644 --- a/src/Bicep.Core/Parsing/BaseParser.cs +++ b/src/Bicep.Core/Parsing/BaseParser.cs @@ -76,9 +76,9 @@ TokenType.Asterisk or TokenType.Plus or TokenType.Minus => 90, - TokenType.GreaterThan or + TokenType.RightChevron or TokenType.GreaterThanOrEqual or - TokenType.LessThan or + TokenType.LeftChevron or TokenType.LessThanOrEqual => 80, TokenType.Equals or diff --git a/src/Bicep.Core/Parsing/Lexer.cs b/src/Bicep.Core/Parsing/Lexer.cs index b32bd5ad197..5eb5d606563 100644 --- a/src/Bicep.Core/Parsing/Lexer.cs +++ b/src/Bicep.Core/Parsing/Lexer.cs @@ -962,7 +962,7 @@ private TokenType ScanToken() return TokenType.LessThanOrEqual; } } - return TokenType.LessThan; + return TokenType.LeftChevron; case '>': if (!textWindow.IsAtEnd()) { @@ -973,7 +973,7 @@ private TokenType ScanToken() return TokenType.GreaterThanOrEqual; } } - return TokenType.GreaterThan; + return TokenType.RightChevron; case '=': if (!textWindow.IsAtEnd()) { diff --git a/src/Bicep.Core/Parsing/TokenType.cs b/src/Bicep.Core/Parsing/TokenType.cs index 4c527c219d8..9212a3cae7c 100644 --- a/src/Bicep.Core/Parsing/TokenType.cs +++ b/src/Bicep.Core/Parsing/TokenType.cs @@ -24,8 +24,8 @@ public enum TokenType Slash, Modulo, Exclamation, - LessThan, - GreaterThan, + LeftChevron, + RightChevron, LessThanOrEqual, GreaterThanOrEqual, Equals, diff --git a/src/Bicep.Core/Syntax/Operators.cs b/src/Bicep.Core/Syntax/Operators.cs index f0e1520e135..9b1a0456ec5 100644 --- a/src/Bicep.Core/Syntax/Operators.cs +++ b/src/Bicep.Core/Syntax/Operators.cs @@ -16,9 +16,9 @@ public static class Operators [TokenType.NotEquals] = BinaryOperator.NotEquals, [TokenType.EqualsInsensitive] = BinaryOperator.EqualsInsensitive, [TokenType.NotEqualsInsensitive] = BinaryOperator.NotEqualsInsensitive, - [TokenType.LessThan] = BinaryOperator.LessThan, + [TokenType.LeftChevron] = BinaryOperator.LessThan, [TokenType.LessThanOrEqual] = BinaryOperator.LessThanOrEqual, - [TokenType.GreaterThan] = BinaryOperator.GreaterThan, + [TokenType.RightChevron] = BinaryOperator.GreaterThan, [TokenType.GreaterThanOrEqual] = BinaryOperator.GreaterThanOrEqual, [TokenType.Plus] = BinaryOperator.Add, [TokenType.Minus] = BinaryOperator.Subtract, @@ -41,4 +41,3 @@ public static class Operators }.ToImmutableDictionary(); } } - diff --git a/src/Bicep.Core/Syntax/SyntaxFactory.cs b/src/Bicep.Core/Syntax/SyntaxFactory.cs index 4e494f7f033..2260bb9d2b9 100644 --- a/src/Bicep.Core/Syntax/SyntaxFactory.cs +++ b/src/Bicep.Core/Syntax/SyntaxFactory.cs @@ -74,8 +74,8 @@ public static Token GetCommaToken(IEnumerable? leadingTrivia = nul public static Token SlashToken => CreateToken(TokenType.Slash); public static Token ModuloToken => CreateToken(TokenType.Modulo); public static Token ExclamationToken => CreateToken(TokenType.Exclamation); - public static Token LessThanToken => CreateToken(TokenType.LessThan); - public static Token GreaterThanToken => CreateToken(TokenType.GreaterThan); + public static Token LessThanToken => CreateToken(TokenType.LeftChevron); + public static Token GreaterThanToken => CreateToken(TokenType.RightChevron); public static Token LessThanOrEqualToken => CreateToken(TokenType.LessThanOrEqual); public static Token GreaterThanOrEqualToken => CreateToken(TokenType.GreaterThanOrEqual); public static Token EqualsToken => CreateToken(TokenType.Equals); diff --git a/src/Bicep.Core/Syntax/SyntaxFacts.cs b/src/Bicep.Core/Syntax/SyntaxFacts.cs index a0eaf7bc7a8..c71cae68b93 100644 --- a/src/Bicep.Core/Syntax/SyntaxFacts.cs +++ b/src/Bicep.Core/Syntax/SyntaxFacts.cs @@ -47,8 +47,8 @@ TokenType.MultilineString or TokenType.Slash => "/", TokenType.Modulo => "%", TokenType.Exclamation => "!", - TokenType.LessThan => "<", - TokenType.GreaterThan => ">", + TokenType.LeftChevron => "<", + TokenType.RightChevron => ">", TokenType.LessThanOrEqual => "<=", TokenType.GreaterThanOrEqual => ">=", TokenType.Equals => "==", diff --git a/src/Bicep.Decompiler/BicepHelpers/SyntaxHelpers.cs b/src/Bicep.Decompiler/BicepHelpers/SyntaxHelpers.cs index 87601191b6f..fba8bb9bebe 100644 --- a/src/Bicep.Decompiler/BicepHelpers/SyntaxHelpers.cs +++ b/src/Bicep.Decompiler/BicepHelpers/SyntaxHelpers.cs @@ -97,9 +97,9 @@ public static class SyntaxHelpers ["mul"] = TokenType.Asterisk, ["div"] = TokenType.Slash, ["mod"] = TokenType.Modulo, - ["less"] = TokenType.LessThan, + ["less"] = TokenType.LeftChevron, ["lessOrEquals"] = TokenType.LessThanOrEqual, - ["greater"] = TokenType.GreaterThan, + ["greater"] = TokenType.RightChevron, ["greaterOrEquals"] = TokenType.GreaterThanOrEqual, ["equals"] = TokenType.Equals, ["and"] = TokenType.LogicalAnd, From 0fae6390acdf458e863b3966fa93ca68356e2a75 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Thu, 2 Nov 2023 12:42:26 -0400 Subject: [PATCH 02/20] Add syntax kind + parser support for utility types --- .../Parsing/ParserTests.cs | 16 ++++++++ src/Bicep.Core/Parsing/BaseParser.cs | 38 ++++++++++++++++-- .../SyntaxLayouts.SyntaxVisitor.cs | 4 ++ src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs | 9 +++++ src/Bicep.Core/Syntax/AstVisitor.cs | 11 ++++++ src/Bicep.Core/Syntax/CstVisitor.cs | 13 +++++++ src/Bicep.Core/Syntax/ISyntaxVisitor.cs | 4 ++ .../Syntax/ParameterizedTypeArgumentSyntax.cs | 19 +++++++++ .../ParameterizedTypeInstantiationSyntax.cs | 39 +++++++++++++++++++ src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs | 29 ++++++++++++++ src/Bicep.Core/Syntax/SyntaxVisitor.cs | 4 ++ 11 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/Bicep.Core/Syntax/ParameterizedTypeArgumentSyntax.cs create mode 100644 src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs diff --git a/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs b/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs index a952db5458f..1a667a254df 100644 --- a/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs +++ b/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs @@ -536,6 +536,22 @@ public void Wildcard_import_should_parse_successfully() fromPath.TryGetLiteralValue().Should().Be("other.bicep"); } + [TestMethod] + public void Utility_type_should_parse_successfully() + { + var typeStatement = "type saType = resource<'Microsoft.Storage/storageAccounts@2022-09-01'>"; + + var parsed = ParserHelper.Parse(typeStatement); + var statement = parsed.Declarations.Single().Should().BeOfType().Subject; + + var imported = statement.Value.Should().BeOfType().Subject; + imported.Name.IdentifierName.Should().Be("resource"); + imported.Arguments.Should().HaveCount(1); + + var singleParam = imported.Arguments.Single().Expression.Should().BeOfType().Subject; + singleParam.TryGetLiteralValue().Should().Be("Microsoft.Storage/storageAccounts@2022-09-01"); + } + private static SyntaxBase RunExpressionTest(string text, string expected, Type expectedRootType) { SyntaxBase expression = ParserHelper.ParseExpression(text); diff --git a/src/Bicep.Core/Parsing/BaseParser.cs b/src/Bicep.Core/Parsing/BaseParser.cs index baf9ccf3a3b..d10909c1813 100644 --- a/src/Bicep.Core/Parsing/BaseParser.cs +++ b/src/Bicep.Core/Parsing/BaseParser.cs @@ -423,11 +423,42 @@ private SyntaxBase FunctionCallOrVariableAccess(ExpressionFlags expressionFlags) return new VariableAccessSyntax(identifier); } - private SyntaxBase TypeVariableAccess() + private SyntaxBase ParameterizedTypeArgument() + { + var expression = this.WithRecovery(TypeExpression, RecoveryFlags.None, TokenType.NewLine, TokenType.Comma, TokenType.RightChevron); + + return new ParameterizedTypeArgumentSyntax(expression); + } + + protected (IdentifierSyntax Identifier, Token OpenChevron, IEnumerable ParameterNodes, Token CloseChevron) ParameterizedTypeInstantiation(IdentifierSyntax utilityTypeName) + { + var openChevron = this.Expect(TokenType.LeftChevron, b => b.ExpectedCharacter("<")); + + var itemsOrTokens = HandleFunctionElements( + closingTokenType: TokenType.RightChevron, + parseChildElement: ParameterizedTypeArgument); + + var closeChevron = this.Expect(TokenType.RightChevron, b => b.ExpectedCharacter(">")); + + return (utilityTypeName, openChevron, itemsOrTokens, closeChevron); + } + + private SyntaxBase UtilityTypeOrTypeVariableAccess() { var identifierToken = Expect(TokenType.Identifier, b => b.ExpectedTypeIdentifier()); var identifier = new IdentifierSyntax(identifierToken); + if (Check(TokenType.LeftChevron)) + { + var utilityType = ParameterizedTypeInstantiation(identifier); + + return new ParameterizedTypeInstantiationSyntax( + utilityType.Identifier, + utilityType.OpenChevron, + utilityType.ParameterNodes, + utilityType.CloseChevron); + } + return new VariableAccessSyntax(identifier); } @@ -1218,7 +1249,7 @@ private SyntaxBase PrimaryTypeExpression() return this.ParenthesizedTypeExpression(); case TokenType.Identifier: - return this.TypeVariableAccess(); + return this.UtilityTypeOrTypeVariableAccess(); default: throw new ExpectedTokenException(nextToken, b => b.UnrecognizedTypeExpression()); @@ -1310,8 +1341,9 @@ protected SyntaxBase ThrowIfSkipped(Func syntaxFunc, DiagnosticBuild protected SyntaxBase Type(bool allowOptionalResourceType) { - if (GetOptionalKeyword(LanguageConstants.ResourceKeyword) is { } resourceKeyword) + if (CheckKeyword(LanguageConstants.ResourceKeyword) && this.reader.PeekAhead(1)?.Type != TokenType.LeftChevron) { + var resourceKeyword = reader.Read(); var type = this.WithRecoveryNullable( () => { diff --git a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs index 5187491745b..59fcccf8083 100644 --- a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs +++ b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs @@ -167,6 +167,10 @@ public SyntaxLayouts(PrettyPrinterV2Context context) public void VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFromClauseSyntax syntax) => this.Apply(syntax, LayoutCompileTimeImportFromClauseSyntax); + public void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) => this.Apply(syntax, LayoutParameterizedTypeInstantiationSyntax); + + public void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) => this.Layout(syntax.Expression); + public IEnumerable Layout(SyntaxBase syntax) { syntax.Accept(this); diff --git a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs index 4fad12f5f2c..cf167688e19 100644 --- a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs +++ b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs @@ -552,6 +552,15 @@ public IEnumerable LayoutWildcardImportSyntax(WildcardImportSyntax syn public IEnumerable LayoutCompileTimeImportFromClauseSyntax(CompileTimeImportFromClauseSyntax syntax) => Spread(syntax.Keyword, syntax.Path); + public IEnumerable LayoutParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + => Glue(syntax.Name, Bracket( + syntax.OpenChevron, + syntax.Children, + syntax.CloseChevron, + separator: CommaLineOrCommaSpace, + padding: LineOrEmpty, + forceBreak: StartsWithNewline(syntax.Children) && syntax.Arguments.Any())); + private IEnumerable LayoutLeadingNodes(IEnumerable leadingNodes) => this.LayoutMany(leadingNodes) .Where(x => x != HardLine); // Remove empty lines between decorators. diff --git a/src/Bicep.Core/Syntax/AstVisitor.cs b/src/Bicep.Core/Syntax/AstVisitor.cs index a8f75018081..a32075f96d8 100644 --- a/src/Bicep.Core/Syntax/AstVisitor.cs +++ b/src/Bicep.Core/Syntax/AstVisitor.cs @@ -415,5 +415,16 @@ public override void VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFro { this.Visit(syntax.Path); } + + public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + { + this.Visit(syntax.Name); + this.VisitNodes(syntax.Children); + } + + public override void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) + { + this.Visit(syntax.Expression); + } } } diff --git a/src/Bicep.Core/Syntax/CstVisitor.cs b/src/Bicep.Core/Syntax/CstVisitor.cs index 00c037851ea..50f48d6311f 100644 --- a/src/Bicep.Core/Syntax/CstVisitor.cs +++ b/src/Bicep.Core/Syntax/CstVisitor.cs @@ -517,5 +517,18 @@ public override void VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFro this.Visit(syntax.Keyword); this.Visit(syntax.Path); } + + public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + { + this.Visit(syntax.Name); + this.Visit(syntax.OpenChevron); + this.VisitNodes(syntax.Children); + this.Visit(syntax.CloseChevron); + } + + public override void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) + { + this.Visit(syntax.Expression); + } } } diff --git a/src/Bicep.Core/Syntax/ISyntaxVisitor.cs b/src/Bicep.Core/Syntax/ISyntaxVisitor.cs index 148337a44d9..4dcb36be627 100644 --- a/src/Bicep.Core/Syntax/ISyntaxVisitor.cs +++ b/src/Bicep.Core/Syntax/ISyntaxVisitor.cs @@ -141,5 +141,9 @@ public interface ISyntaxVisitor void VisitWildcardImportSyntax(WildcardImportSyntax syntax); void VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFromClauseSyntax syntax); + + void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax); + + void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax); } } diff --git a/src/Bicep.Core/Syntax/ParameterizedTypeArgumentSyntax.cs b/src/Bicep.Core/Syntax/ParameterizedTypeArgumentSyntax.cs new file mode 100644 index 00000000000..7d3c8e730db --- /dev/null +++ b/src/Bicep.Core/Syntax/ParameterizedTypeArgumentSyntax.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public class ParameterizedTypeArgumentSyntax : ExpressionSyntax +{ + public ParameterizedTypeArgumentSyntax(SyntaxBase expression) + { + this.Expression = expression; + } + + public SyntaxBase Expression { get; } + + public override void Accept(ISyntaxVisitor visitor) => visitor.VisitParameterizedTypeArgumentSyntax(this); + + public override TextSpan Span => this.Expression.Span; +} diff --git a/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs b/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs new file mode 100644 index 00000000000..9712d2298d2 --- /dev/null +++ b/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public class ParameterizedTypeInstantiationSyntax : TypeSyntax +{ + public ParameterizedTypeInstantiationSyntax(IdentifierSyntax name, Token openChevron, IEnumerable children, Token closeChevron) + { + AssertTokenType(openChevron, nameof(openChevron), TokenType.LeftChevron); + AssertTokenType(closeChevron, nameof(closeChevron), TokenType.RightChevron); + + this.Name = name; + this.OpenChevron = openChevron; + this.Children = children.ToImmutableArray(); + this.CloseChevron = closeChevron; + this.Arguments = children.OfType().ToImmutableArray(); + } + + public IdentifierSyntax Name { get; } + + public Token OpenChevron { get; } + + public ImmutableArray Children { get; } + + public ImmutableArray Arguments { get; } + + public Token CloseChevron { get; } + + public ParameterizedTypeArgumentSyntax GetArgumentByPosition(int index) => Arguments[index]; + + public override TextSpan Span => TextSpan.Between(Name, CloseChevron); + + public override void Accept(ISyntaxVisitor visitor) => visitor.VisitParameterizedTypeInstantiationSyntax(this); +} diff --git a/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs b/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs index 8882a2ca27f..b6b41c9a1ff 100644 --- a/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs +++ b/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs @@ -1140,5 +1140,34 @@ protected virtual SyntaxBase ReplaceCompileTimeImportFromClauseSyntax(CompileTim return new CompileTimeImportFromClauseSyntax(keyword, path); } void ISyntaxVisitor.VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFromClauseSyntax syntax) => ReplaceCurrent(syntax, ReplaceCompileTimeImportFromClauseSyntax); + + protected virtual SyntaxBase ReplaceParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + { + var hasChanges = TryRewriteStrict(syntax.Name, out var name); + hasChanges |= TryRewriteStrict(syntax.OpenChevron, out var openChevron); + hasChanges |= TryRewriteStrict(syntax.Children, out var children); + hasChanges |= TryRewriteStrict(syntax.CloseChevron, out var closeChevron); + + if (!hasChanges) + { + return syntax; + } + + return new ParameterizedTypeInstantiationSyntax(name, openChevron, children, closeChevron); + } + void ISyntaxVisitor.VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) => ReplaceCurrent(syntax, ReplaceParameterizedTypeInstantiationSyntax); + + protected virtual SyntaxBase ReplaceParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) + { + var hasChanges = TryRewrite(syntax.Expression, out var expression); + + if (!hasChanges) + { + return syntax; + } + + return new ParameterizedTypeArgumentSyntax(expression); + } + void ISyntaxVisitor.VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) => ReplaceCurrent(syntax, ReplaceParameterizedTypeArgumentSyntax); } } diff --git a/src/Bicep.Core/Syntax/SyntaxVisitor.cs b/src/Bicep.Core/Syntax/SyntaxVisitor.cs index 05bd768ab24..9dbfbdea917 100644 --- a/src/Bicep.Core/Syntax/SyntaxVisitor.cs +++ b/src/Bicep.Core/Syntax/SyntaxVisitor.cs @@ -147,6 +147,10 @@ public abstract class SyntaxVisitor : ISyntaxVisitor public abstract void VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFromClauseSyntax syntax); + public abstract void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax); + + public abstract void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax); + public void Visit(SyntaxBase? node) { if (node == null) From cd0b425a8fbc6de27087b6a108600156020bb391 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Thu, 30 Nov 2023 13:09:10 -0500 Subject: [PATCH 03/20] Add a utility type for declaring a type that matches the entirety of a resource's body --- .../CompileTimeImportTests.cs | 54 ++++ .../UserDefinedTypeTests.cs | 111 +++++++ .../ArmTemplateSemanticModelTests.cs | 79 +++++ .../ResourceDerivedTypeBinderTests.cs | 270 +++++++++++++++++ .../Diagnostics/DiagnosticBuilder.cs | 13 +- src/Bicep.Core/Emit/TemplateWriter.cs | 20 +- src/Bicep.Core/Intermediate/Expression.cs | 10 + .../Intermediate/ExpressionBuilder.cs | 4 + .../Intermediate/ExpressionRewriteVisitor.cs | 6 + .../Intermediate/ExpressionVisitor.cs | 4 + .../Intermediate/IExpressionVisitor.cs | 2 + src/Bicep.Core/LanguageConstants.cs | 1 + src/Bicep.Core/Semantics/ITypeManager.cs | 2 + .../Semantics/NameBindingVisitor.cs | 15 + .../Namespaces/SystemNamespaceType.cs | 32 +- src/Bicep.Core/Semantics/TypeAliasSymbol.cs | 2 - .../TypeSystem/ArmTemplateTypeLoader.cs | 87 +++++- .../TypeSystem/DeclaredTypeManager.cs | 146 +++------ .../Providers/Az/AzResourceTypeFactory.cs | 8 +- .../TypeSystem/ResourceDerivedTypeBinder.cs | 282 ++++++++++++++++++ .../TypeSystem/TypeAssignmentVisitor.cs | 13 +- src/Bicep.Core/TypeSystem/TypeHelper.cs | 81 +++++ src/Bicep.Core/TypeSystem/TypeKind.cs | 5 + src/Bicep.Core/TypeSystem/TypeManager.cs | 2 + .../Types/IUnboundResourceDerivedType.cs | 25 ++ .../TypeSystem/Types/TypeParameter.cs | 18 ++ .../TypeSystem/Types/TypeTemplate.cs | 56 ++++ ...UnboundResourceDerivedPartialObjectType.cs | 29 ++ .../Types/UnboundResourceDerivedType.cs | 22 ++ 29 files changed, 1277 insertions(+), 122 deletions(-) create mode 100644 src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs create mode 100644 src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs create mode 100644 src/Bicep.Core/TypeSystem/Types/IUnboundResourceDerivedType.cs create mode 100644 src/Bicep.Core/TypeSystem/Types/TypeParameter.cs create mode 100644 src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs create mode 100644 src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedPartialObjectType.cs create mode 100644 src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedType.cs diff --git a/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs b/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs index 0e205caf6fe..5852d27b0b4 100644 --- a/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs +++ b/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs @@ -1934,4 +1934,58 @@ func isWindows(hostingPlanName string) string => contains('-windows-', hostingPl var evaluated = TemplateEvaluator.Evaluate(result.Template); evaluated.Should().HaveValueAtPath("outputs.out.value", "ASP999"); } + + [TestMethod] + public void Resource_derived_types_are_bound_when_imported_from_ARM_JSON_models() + { + var result = CompilationHelper.Compile(ServicesWithCompileTimeTypeImports, + ("main.bicep", """ + import {foo} from 'mod.json' + + param location string = resourceGroup().location + param fooParam foo = { + bar: { + name: 'acct' + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + unknownProperty: false + } + } + } + + output fooOutput foo = fooParam + """), + ("mod.json", $$""" + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "definitions": { + "foo": { + "metadata": { + "{{LanguageConstants.MetadataExportedPropertyName}}": true + }, + "type": "object", + "additionalProperties": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + }, + "resources": [] + } + """)); + + result.Should().NotHaveAnyCompilationBlockingDiagnostics(); + result.Should().HaveDiagnostics(new[] + { + ("BCP037", DiagnosticLevel.Warning, """The property "unknownProperty" is not allowed on objects of type "StorageAccountPropertiesCreateParametersOrStorageAccountProperties". Permissible properties include "accessTier", "allowBlobPublicAccess", "allowCrossTenantReplication", "allowedCopyScope", "allowSharedKeyAccess", "azureFilesIdentityBasedAuthentication", "customDomain", "defaultToOAuthAuthentication", "dnsEndpointType", "encryption", "immutableStorageWithVersioning", "isHnsEnabled", "isLocalUserEnabled", "isNfsV3Enabled", "isSftpEnabled", "keyPolicy", "largeFileSharesState", "minimumTlsVersion", "networkAcls", "publicNetworkAccess", "routingPreference", "sasPolicy", "supportsHttpsTrafficOnly"."""), + }); + } } diff --git a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs index d564ccef80e..ef4d2f8ef50 100644 --- a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs +++ b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs @@ -862,4 +862,115 @@ public void Parsing_incomplete_tuple_type_expressions_halts() result.Template.Should().BeNull(); } + + [TestMethod] + public void Resource_derived_type_should_compile_successfully() + { + var result = CompilationHelper.Compile(""" + type myType = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + """); + + result.Template.Should().HaveValueAtPath("definitions", JToken.Parse($$""" + { + "myType": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + """)); + } + + [TestMethod] + public void Param_with_resource_derived_type_can_be_loaded() + { + var result = CompilationHelper.Compile( + ("main.bicep", """ + param location string = resourceGroup().location + + module mod 'mod.json' = { + name: 'mod' + params: { + foo: { + bar: { + name: 'acct' + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + unknownProperty: false + } + } + } + } + } + """), + ("mod.json", $$""" + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "parameters": { + "foo": { + "type": "object", + "additionalProperties": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + }, + "resources": [] + } + """)); + + result.Should().NotHaveAnyCompilationBlockingDiagnostics(); + result.Should().HaveDiagnostics(new[] + { + ("BCP037", DiagnosticLevel.Warning, """The property "unknownProperty" is not allowed on objects of type "StorageAccountPropertiesCreateParametersOrStorageAccountProperties". Permissible properties include "accessTier", "allowBlobPublicAccess", "allowCrossTenantReplication", "allowedCopyScope", "allowSharedKeyAccess", "azureFilesIdentityBasedAuthentication", "customDomain", "defaultToOAuthAuthentication", "dnsEndpointType", "encryption", "immutableStorageWithVersioning", "isHnsEnabled", "isLocalUserEnabled", "isNfsV3Enabled", "isSftpEnabled", "keyPolicy", "largeFileSharesState", "minimumTlsVersion", "networkAcls", "publicNetworkAccess", "routingPreference", "sasPolicy", "supportsHttpsTrafficOnly"."""), + }); + } + + [TestMethod] + public void Output_with_resource_derived_type_can_be_loaded() + { + var result = CompilationHelper.Compile( + ("main.bicep", """ + module mod 'mod.json' = { + name: 'mod' + } + + output out string = mod.outputs.foo.bar.properties.unknownProperty + """), + ("mod.json", $$""" + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "outputs": { + "foo": { + "type": "object", + "additionalProperties": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Storage/storageAccounts@2022-09-01" + } + }, + "value": {} + } + }, + "resources": [] + } + """)); + + result.Should().NotHaveAnyCompilationBlockingDiagnostics(); + result.Should().HaveDiagnostics(new[] + { + ("BCP053", DiagnosticLevel.Warning, """The type "StorageAccountPropertiesCreateParametersOrStorageAccountProperties" does not contain property "unknownProperty". Available properties include "accessTier", "allowBlobPublicAccess", "allowCrossTenantReplication", "allowedCopyScope", "allowSharedKeyAccess", "azureFilesIdentityBasedAuthentication", "blobRestoreStatus", "creationTime", "customDomain", "defaultToOAuthAuthentication", "dnsEndpointType", "encryption", "failoverInProgress", "geoReplicationStats", "immutableStorageWithVersioning", "isHnsEnabled", "isLocalUserEnabled", "isNfsV3Enabled", "isSftpEnabled", "keyCreationTime", "keyPolicy", "largeFileSharesState", "lastGeoFailoverTime", "minimumTlsVersion", "networkAcls", "primaryEndpoints", "primaryLocation", "privateEndpointConnections", "provisioningState", "publicNetworkAccess", "routingPreference", "sasPolicy", "secondaryEndpoints", "secondaryLocation", "statusOfPrimary", "statusOfSecondary", "storageAccountSkuConversionStatus", "supportsHttpsTrafficOnly"."""), + }); + } } diff --git a/src/Bicep.Core.UnitTests/Semantics/ArmTemplateSemanticModelTests.cs b/src/Bicep.Core.UnitTests/Semantics/ArmTemplateSemanticModelTests.cs index 734228ae676..20341a2e98e 100644 --- a/src/Bicep.Core.UnitTests/Semantics/ArmTemplateSemanticModelTests.cs +++ b/src/Bicep.Core.UnitTests/Semantics/ArmTemplateSemanticModelTests.cs @@ -436,6 +436,85 @@ public void Model_creates_tagged_union_types_from_nullable_modifier() taggedUnionType.UnionMembersByKey["'b'"].Properties["value"].TypeReference.Type.Should().Be(LanguageConstants.Int); } + [TestMethod] + public void Resource_derivation_metadata_causes_type_to_be_loaded_as_unbound_resource_derived_type() + { + var parameterType = GetLoadedParameterType($$""" + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "languageVersion": "2.0", + "resources": {}, + "parameters": { + "arrayOfStorageAccounts": { + "type": "array", + "items": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + } + } + """, + "arrayOfStorageAccounts"); + + var typedArrayParameterType = parameterType.Should().BeOfType().Subject; + var itemType = typedArrayParameterType.Item.Should().BeOfType().Subject; + + itemType.TypeReference.Type.Should().Be("Microsoft.Storage/storageAccounts"); + itemType.TypeReference.ApiVersion.Should().Be("2022-09-01"); + itemType.FallbackType.Should().Be(LanguageConstants.Object); + } + + [TestMethod] + public void Resource_derivation_metadata_causes_tagged_union_variant_to_be_loaded_as_unbound_resource_derived_type() + { + var parameterType = GetLoadedParameterType($$""" + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "languageVersion": "2.0", + "resources": {}, + "parameters": { + "taggedUnion": { + "type": "object", + "discriminator": { + "propertyName": "name", + "mapping": { + "foo": { + "type": "object", + "properties": { + "name": { + "type": "string", + "allowedValues": ["foo"] + } + } + }, + "default": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Resources/tags@2022-09-01" + } + } + } + } + } + } + } + """, + "taggedUnion"); + + var typedArrayParameterType = parameterType.Should().BeOfType().Subject; + var itemType = typedArrayParameterType.UnionMembersByKey["'default'"].Should().BeOfType().Subject; + + itemType.TypeReference.Type.Should().Be("Microsoft.Resources/tags"); + itemType.TypeReference.ApiVersion.Should().Be("2022-09-01"); + var fallbackType = itemType.FallbackType.Should().BeAssignableTo().Subject; + fallbackType.Properties["name"].TypeReference.Type.Should().Be(TypeFactory.CreateStringLiteralType("default")); + } + private static TypeSymbol GetLoadedParameterType(string jsonTemplate, string parameterName) { var model = LoadModel(jsonTemplate); diff --git a/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs b/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs new file mode 100644 index 00000000000..f050973c54d --- /dev/null +++ b/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs @@ -0,0 +1,270 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bicep.Core.Diagnostics; +using Bicep.Core.Extensions; +using Bicep.Core.Features; +using Bicep.Core.Parsing; +using Bicep.Core.Resources; +using Bicep.Core.Semantics; +using Bicep.Core.Semantics.Namespaces; +using Bicep.Core.TypeSystem; +using Bicep.Core.TypeSystem.Providers; +using Bicep.Core.TypeSystem.Types; +using Bicep.Core.UnitTests.Assertions; +using Bicep.Core.UnitTests.Mock; +using Bicep.Core.Workspaces; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Bicep.Core.UnitTests.TypeSystem; + +[TestClass] +public class ResourceDerivedTypeBinderTests +{ + [DataTestMethod] + [DynamicData(nameof(GetTypesNotInNeedOfBinding), DynamicDataSourceType.Method)] + public void Returns_input_if_no_unbound_types_are_enclosed(TypeSymbol type) + { + ResourceDerivedTypeBinder sut = new(StrictMock.Of().Object, new SimpleDiagnosticWriter(), TextSpan.TextDocumentStart); + sut.BindResourceDerivedTypes(type).Should().BeSameAs(type); + } + + private static IEnumerable GetTypesNotInNeedOfBinding() + { + static object[] Row(TypeSymbol type) => new object[] { type }; + + yield return Row(LanguageConstants.Any); + yield return Row(LanguageConstants.Null); + yield return Row(LanguageConstants.Bool); + yield return Row(LanguageConstants.String); + yield return Row(LanguageConstants.SecureString); + yield return Row(LanguageConstants.Int); + yield return Row(LanguageConstants.Array); + yield return Row(LanguageConstants.Object); + yield return Row(LanguageConstants.SecureObject); + yield return Row(LanguageConstants.SecureString); + + // recursive object + ObjectType? myObject = null; + myObject = new("recursive", + TypeSymbolValidationFlags.Default, + new TypeProperty("property", new DeferredTypeReference(() => myObject!)).AsEnumerable(), + null); + yield return Row(myObject); + } + + [TestMethod] + public void Hydrates_array_item_types() + { + var hydrated = TypeFactory.CreateBooleanLiteralType(false); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = TypeFactory.CreateArrayType(new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)); + sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType() + .Subject.Item.Should().BeSameAs(hydrated); + } + + [TestMethod] + public void Hydrates_tuple_item_types() + { + var hydrated = TypeFactory.CreateBooleanLiteralType(false); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = new TupleType(ImmutableArray.Create(new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)), + TypeSymbolValidationFlags.Default); + sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType() + .Subject.Items.First().Should().BeSameAs(hydrated); + } + + [TestMethod] + public void Hydrates_discriminated_object_variant_types() + { + var hydrated = new ObjectType("foo", + TypeSymbolValidationFlags.Default, + new TypeProperty("property", TypeFactory.CreateStringLiteralType("foo")).AsEnumerable(), + null); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = new DiscriminatedObjectType("discriminatedObject", + TypeSymbolValidationFlags.Default, + "property", + new ITypeReference[] + { + new UnboundResourceDerivedPartialObjectType(unhydratedTypeRef, "property", "foo"), + new ObjectType("bar", + TypeSymbolValidationFlags.Default, + new TypeProperty("property", TypeFactory.CreateStringLiteralType("bar")).AsEnumerable(), + null) + }); + + sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType() + .Subject.UnionMembersByKey["'foo'"].Should().BeSameAs(hydrated); + } + + [TestMethod] + public void Hydrates_object_property_types() + { + var hydrated = TypeFactory.CreateBooleanLiteralType(false); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = new ObjectType("object", + TypeSymbolValidationFlags.Default, + new TypeProperty("property", new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)).AsEnumerable(), + null); + + sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType() + .Subject.Properties["property"].TypeReference.Type.Should().BeSameAs(hydrated); + } + + [TestMethod] + public void Hydrates_object_additinalProperties_types() + { + var hydrated = TypeFactory.CreateBooleanLiteralType(false); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = new ObjectType("object", + TypeSymbolValidationFlags.Default, + ImmutableArray.Empty, + new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)); + + var hydratedContainer = sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType().Subject; + hydratedContainer.AdditionalPropertiesType.Should().NotBeNull(); + hydratedContainer.AdditionalPropertiesType!.Type.Should().BeSameAs(hydrated); + } + + [TestMethod] + public void Hydrates_union_member_types() + { + var hydrated = TypeFactory.CreateBooleanLiteralType(false); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = TypeHelper.CreateTypeUnion(LanguageConstants.String, + new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)); + + sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType() + .Subject.Members.Should().Contain(hydrated); + } + + [TestMethod] + public void Hydrates_wrapped_types() + { + var hydrated = TypeFactory.CreateBooleanLiteralType(false); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = new TypeType(new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)); + + sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType() + .Subject.Unwrapped.Should().BeSameAs(hydrated); + } + + private static (ResourceDerivedTypeBinder sut, ResourceTypeReference unhydratedTypeRef) SetupBinder(TypeSymbol hydratedType) + { + var unhydratedTypeRef = new ResourceTypeReference("type", "version"); + var resourceTypeProviderMock = StrictMock.Of(); + var stubbedNamespaceType = new NamespaceType(AzNamespaceType.BuiltInName, + AzNamespaceType.Settings, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + resourceTypeProviderMock.Object); + resourceTypeProviderMock.Setup(x => x.TryGetDefinedType(stubbedNamespaceType, unhydratedTypeRef, ResourceTypeGenerationFlags.None)) + .Returns(new ResourceType(stubbedNamespaceType, + unhydratedTypeRef, + ResourceScope.None, + ResourceScope.None, + ResourceFlags.None, + hydratedType, + ImmutableHashSet.Empty)); + + var namespaceProviderMock = StrictMock.Of(); + namespaceProviderMock.Setup(x => x.TryGetNamespace(It.Is(x => x.Name == AzNamespaceType.BuiltInName), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(stubbedNamespaceType); + namespaceProviderMock.Setup(x => x.TryGetNamespace(It.Is(x => x.Name != AzNamespaceType.BuiltInName), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((NamespaceType?)null); + + var scopeMock = StrictMock.Of(); + scopeMock.Setup(x => x.GetDeclarationsByName(It.IsAny())) + .Returns(Enumerable.Empty()); + scopeMock.Setup(x => x.Declarations) + .Returns(Enumerable.Empty()); + + var resolver = NamespaceResolver.Create(BicepTestConstants.Features, + namespaceProviderMock.Object, + SourceFileFactory.CreateBicepFile(new("file:///path/to/main.bicep"), string.Empty), + ResourceScope.None, + scopeMock.Object); + + var binderMock = StrictMock.Of(); + binderMock.Setup(x => x.NamespaceResolver).Returns(resolver); + + return (new(binderMock.Object, new SimpleDiagnosticWriter(), TextSpan.TextDocumentStart), unhydratedTypeRef); + } + + [TestMethod] + public void Emits_diagnostic_and_uses_fallback_type_when_resource_not_found() + { + var unhydratedTypeRef = new ResourceTypeReference("type", "version"); + var resourceTypeProviderMock = StrictMock.Of(); + var stubbedNamespaceType = new NamespaceType(AzNamespaceType.BuiltInName, + AzNamespaceType.Settings, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + resourceTypeProviderMock.Object); + resourceTypeProviderMock.Setup(x => x.TryGetDefinedType(stubbedNamespaceType, unhydratedTypeRef, ResourceTypeGenerationFlags.None)) + .Returns((ResourceType?)null); + resourceTypeProviderMock.Setup(x => x.TryGenerateFallbackType(stubbedNamespaceType, unhydratedTypeRef, ResourceTypeGenerationFlags.None)) + .Returns((ResourceType?)null); + + var namespaceProviderMock = StrictMock.Of(); + namespaceProviderMock.Setup(x => x.TryGetNamespace(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(stubbedNamespaceType); + namespaceProviderMock.Setup(x => x.TryGetNamespace(It.Is(x => x.Name != AzNamespaceType.BuiltInName), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((NamespaceType?)null); + + var scopeMock = StrictMock.Of(); + scopeMock.Setup(x => x.GetDeclarationsByName(It.IsAny())) + .Returns(Enumerable.Empty()); + scopeMock.Setup(x => x.Declarations) + .Returns(Enumerable.Empty()); + + var resolver = NamespaceResolver.Create(BicepTestConstants.Features, + namespaceProviderMock.Object, + SourceFileFactory.CreateBicepFile(new("file:///path/to/main.bicep"), string.Empty), + ResourceScope.None, + scopeMock.Object); + + var binderMock = StrictMock.Of(); + binderMock.Setup(x => x.NamespaceResolver).Returns(resolver); + + var diagnostics = ToListDiagnosticWriter.Create(); + ResourceDerivedTypeBinder sut = new(binderMock.Object, diagnostics, TextSpan.TextDocumentStart); + var fallbackType = LanguageConstants.SecureString; + + sut.BindResourceDerivedTypes(new UnboundResourceDerivedType(unhydratedTypeRef, fallbackType)).Should().BeSameAs(fallbackType); + diagnostics.GetDiagnostics().Should() + .ContainSingleDiagnostic("BCP081", DiagnosticLevel.Warning, """Resource type "type@version" does not have types available."""); + + resourceTypeProviderMock.Verify(x => x.TryGetDefinedType(stubbedNamespaceType, unhydratedTypeRef, ResourceTypeGenerationFlags.None), Times.Once()); + resourceTypeProviderMock.Verify(x => x.TryGenerateFallbackType(stubbedNamespaceType, unhydratedTypeRef, ResourceTypeGenerationFlags.None), Times.Once()); + } +} diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 2e90de88c21..152993088f9 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -2097,8 +2097,17 @@ public FixableDiagnostic ProviderDeclarationViaImportKeywordIsDeprecated(Provide public ErrorDiagnostic MalformedProviderPackage(string ociManifestPath) => new( TextSpan, "BCP382", - $"The provider package is malformed and could not be loaded from \"{ociManifestPath}\"." - ); + $"The provider package is malformed and could not be loaded from \"{ociManifestPath}\"."); + + public ErrorDiagnostic TypeIsNotParameterizable(string typeName) => new( + TextSpan, + "BCP383", + $"The \"{typeName}\" type is not parameterizable."); + + public ErrorDiagnostic TypeRequiresParameterization(string typeName, int requiredArgumentCount) => new( + TextSpan, + "BCP383", + $"The \"{typeName}\" type requires {requiredArgumentCount} argument(s)."); } public static DiagnosticBuilderInternal ForPosition(TextSpan span) diff --git a/src/Bicep.Core/Emit/TemplateWriter.cs b/src/Bicep.Core/Emit/TemplateWriter.cs index 1258a7dca54..1fc697c5af6 100644 --- a/src/Bicep.Core/Emit/TemplateWriter.cs +++ b/src/Bicep.Core/Emit/TemplateWriter.cs @@ -421,6 +421,7 @@ FullyQualifiedAmbientTypeReferenceExpression fullyQualifiedAmbientTypeReference // resource types ResourceTypeExpression resourceType => GetTypePropertiesForResourceType(resourceType), + ResourceDerivedTypeExpression resourceDerivedType => GetTypePropertiesForResourceDerivedType(resourceDerivedType), // aggregate types ArrayTypeExpression arrayType => GetTypePropertiesForArrayType(arrayType), @@ -476,6 +477,23 @@ private static ObjectExpression GetTypePropertiesForResourceType(ResourceTypeExp }); } + private static ObjectExpression GetTypePropertiesForResourceDerivedType(ResourceDerivedTypeExpression expression) + { + var typeString = expression.RootResourceType.TypeReference.FormatName(); + + return ExpressionFactory.CreateObject(new[] + { + TypeProperty(GetNonLiteralTypeName(expression.ExpressedType), expression.SourceSyntax), + ExpressionFactory.CreateObjectProperty(LanguageConstants.ParameterMetadataPropertyName, + ExpressionFactory.CreateObject( + ExpressionFactory.CreateObjectProperty(LanguageConstants.MetadataResourceDerivedTypePropertyName, + ExpressionFactory.CreateStringLiteral(typeString, expression.SourceSyntax), + expression.SourceSyntax).AsEnumerable(), + expression.SourceSyntax), + expression.SourceSyntax), + }); + } + private ObjectExpression GetTypePropertiesForArrayType(ArrayTypeExpression expression) { var properties = new List { TypeProperty(LanguageConstants.ArrayType, expression.SourceSyntax) }; @@ -685,7 +703,7 @@ private IEnumerable GetDiscriminatedUnionMappingEntrie StringLiteralType or StringType => "string", IntegerLiteralType or IntegerType => "int", BooleanLiteralType or BooleanType => "bool", - ObjectType => "object", + ObjectType or DiscriminatedObjectType => "object", ArrayType => "array", // This would have been caught by the DeclaredTypeManager during initial type assignment _ => throw new ArgumentException("Unresolvable type name"), diff --git a/src/Bicep.Core/Intermediate/Expression.cs b/src/Bicep.Core/Intermediate/Expression.cs index 38686cefa2e..96471c49d11 100644 --- a/src/Bicep.Core/Intermediate/Expression.cs +++ b/src/Bicep.Core/Intermediate/Expression.cs @@ -901,3 +901,13 @@ public record ParameterKeyVaultReferenceExpression( public override void Accept(IExpressionVisitor visitor) => visitor.VisitParameterKeyVaultReferenceExpression(this); } + +public record ResourceDerivedTypeExpression( + SyntaxBase? SourceSyntax, + ResourceType RootResourceType, + TypeSymbol ExpressedType +) : TypeExpression(SourceSyntax, ExpressedType) +{ + public override void Accept(IExpressionVisitor visitor) + => visitor.VisitResourceDerivedTypeExpression(this); +} diff --git a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs index 77f05d3ef3b..7dd78be5587 100644 --- a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs +++ b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs @@ -296,6 +296,10 @@ UnionTypeSyntax unionTypeSyntax when Context.SemanticModel.GetTypeInfo(unionType NonNullAssertionSyntax nonNullAssertion => new NonNullableTypeExpression(nonNullAssertion, ConvertTypeWithoutLowering(nonNullAssertion.BaseExpression)), PropertyAccessSyntax propertyAccess => ConvertPropertyAccessInTypeExpression(propertyAccess), ArrayAccessSyntax arrayAccess => ConvertPropertyAccessInTypeExpression(arrayAccess), + ParameterizedTypeInstantiationSyntax parameterizedTypeInstantiation + => Context.SemanticModel.TypeManager.TryGetReifiedType(parameterizedTypeInstantiation) is TypeExpression reified + ? reified + : throw new ArgumentException($"Failed to reify parameterized type invocation."), _ => throw new ArgumentException($"Failed to convert syntax of type {syntax.GetType()}"), }; diff --git a/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs b/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs index b62feb9703e..732fe4b3301 100644 --- a/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs +++ b/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs @@ -656,6 +656,12 @@ public virtual Expression ReplaceWildcardImportVariablePropertyReferenceExpressi return expression; } + void IExpressionVisitor.VisitResourceDerivedTypeExpression(ResourceDerivedTypeExpression expression) => ReplaceCurrent(expression, ReplaceResourceDerivedTypeExpression); + public virtual Expression ReplaceResourceDerivedTypeExpression(ResourceDerivedTypeExpression expression) + { + return expression; + } + void IExpressionVisitor.VisitProgramExpression(ProgramExpression expression) => ReplaceCurrent(expression, ReplaceProgramExpression); public virtual Expression ReplaceProgramExpression(ProgramExpression expression) { diff --git a/src/Bicep.Core/Intermediate/ExpressionVisitor.cs b/src/Bicep.Core/Intermediate/ExpressionVisitor.cs index f2174e23c59..b9dc5beb737 100644 --- a/src/Bicep.Core/Intermediate/ExpressionVisitor.cs +++ b/src/Bicep.Core/Intermediate/ExpressionVisitor.cs @@ -367,6 +367,10 @@ public virtual void VisitWildcardImportVariablePropertyReferenceExpression(Wildc { } + public virtual void VisitResourceDerivedTypeExpression(ResourceDerivedTypeExpression expression) + { + } + public virtual void VisitProgramExpression(ProgramExpression expression) { Visit(expression.Metadata); diff --git a/src/Bicep.Core/Intermediate/IExpressionVisitor.cs b/src/Bicep.Core/Intermediate/IExpressionVisitor.cs index f76fae698a5..513232245f8 100644 --- a/src/Bicep.Core/Intermediate/IExpressionVisitor.cs +++ b/src/Bicep.Core/Intermediate/IExpressionVisitor.cs @@ -140,4 +140,6 @@ public interface IExpressionVisitor void VisitImportedUserDefinedFunctionCallExpression(ImportedUserDefinedFunctionCallExpression expression); void VisitWildcardImportInstanceFunctionCallExpression(WildcardImportInstanceFunctionCallExpression expression); + + void VisitResourceDerivedTypeExpression(ResourceDerivedTypeExpression expression); } diff --git a/src/Bicep.Core/LanguageConstants.cs b/src/Bicep.Core/LanguageConstants.cs index 7b85aee13e7..1f20434d9eb 100644 --- a/src/Bicep.Core/LanguageConstants.cs +++ b/src/Bicep.Core/LanguageConstants.cs @@ -141,6 +141,7 @@ public static class LanguageConstants public const string ParameterSealedPropertyName = "sealed"; public const string MetadataDescriptionPropertyName = "description"; public const string MetadataResourceTypePropertyName = "resourceType"; + public const string MetadataResourceDerivedTypePropertyName = "__bicep_resource_derived_type!"; public const string MetadataExportedPropertyName = "__bicep_export!"; public const string MetadataImportedFromPropertyName = "__bicep_imported_from!"; public const string TemplateMetadataExportedVariablesName = "__bicep_exported_variables!"; diff --git a/src/Bicep.Core/Semantics/ITypeManager.cs b/src/Bicep.Core/Semantics/ITypeManager.cs index 803d3cffba6..f96a316c438 100644 --- a/src/Bicep.Core/Semantics/ITypeManager.cs +++ b/src/Bicep.Core/Semantics/ITypeManager.cs @@ -21,5 +21,7 @@ public interface ITypeManager FunctionOverload? GetMatchedFunctionOverload(FunctionCallSyntaxBase syntax); Expression? GetMatchedFunctionResultValue(FunctionCallSyntaxBase syntax); + + TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntax syntax); } } diff --git a/src/Bicep.Core/Semantics/NameBindingVisitor.cs b/src/Bicep.Core/Semantics/NameBindingVisitor.cs index 6457c6be938..af24930a8bf 100644 --- a/src/Bicep.Core/Semantics/NameBindingVisitor.cs +++ b/src/Bicep.Core/Semantics/NameBindingVisitor.cs @@ -283,6 +283,21 @@ public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax) this.bindings.Add(syntax, symbol); } + public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + { + FunctionFlags currentFlags = allowedFlags; + Visit(syntax.Name); + Visit(syntax.OpenChevron); + allowedFlags = allowedFlags.HasAnyDecoratorFlag() ? FunctionFlags.Default : allowedFlags; + VisitNodes(syntax.Children); + allowedFlags = currentFlags; + Visit(syntax.CloseChevron); + + var symbol = this.LookupSymbolByName(syntax.Name, false); + + this.bindings.Add(syntax, symbol); + } + protected override void VisitInternal(SyntaxBase syntax) { // any node can be a binding scope diff --git a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs index 48f943196d3..b6b71caa1bb 100644 --- a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs +++ b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs @@ -1728,15 +1728,43 @@ ObjectTypeSyntax @object when TypeHelper.IsLiteralType(typeManager.GetDeclaredTy _ => false, }; - private static IEnumerable GetSystemAmbientSymbols() + private static IEnumerable GetSystemAmbientSymbols(BicepSourceFileKind sourceFileKind) + => sourceFileKind switch + { + BicepSourceFileKind.ParamsFile => ImmutableArray.Empty, + _ => GetArmPrimitiveTypes().Concat(GetBuiltInUtilityTypes()), + }; + + private static IEnumerable GetArmPrimitiveTypes() => LanguageConstants.DeclarationTypes.Select(t => new TypeProperty(t.Key, new TypeType(t.Value))); + private static IEnumerable GetBuiltInUtilityTypes() + { + yield return new("resource", new TypeTemplate("resource", + ImmutableArray.Create(new TypeParameter("T")), + (binder, syntax, argumentTypes) => + { + if (argumentTypes.FirstOrDefault() is not StringLiteralType stringLiteral) + { + return new(DiagnosticBuilder.ForPosition(TextSpan.BetweenExclusive(syntax.OpenChevron, syntax.CloseChevron)).CompileTimeConstantRequired()); + } + + if (!TypeHelper.GetResourceTypeFromString(binder, stringLiteral.RawStringValue, ResourceTypeGenerationFlags.None, parentResourceType: null) + .IsSuccess(out var resourceType, out var errorBuilder)) + { + return new(errorBuilder(DiagnosticBuilder.ForPosition(syntax.GetArgumentByPosition(0)))); + } + + return new(new ResourceDerivedTypeExpression(syntax, resourceType, resourceType.Body.Type)); + })); + } + public static NamespaceType Create(string aliasName, IFeatureProvider featureProvider, BicepSourceFileKind sourceFileKind) { return new NamespaceType( aliasName, Settings, - GetSystemAmbientSymbols(), + GetSystemAmbientSymbols(sourceFileKind), GetSystemOverloads(featureProvider, sourceFileKind), BannedFunctions, GetSystemDecorators(featureProvider, sourceFileKind), diff --git a/src/Bicep.Core/Semantics/TypeAliasSymbol.cs b/src/Bicep.Core/Semantics/TypeAliasSymbol.cs index ce750f918bd..9378631f834 100644 --- a/src/Bicep.Core/Semantics/TypeAliasSymbol.cs +++ b/src/Bicep.Core/Semantics/TypeAliasSymbol.cs @@ -18,8 +18,6 @@ public TypeAliasSymbol(ISymbolContext context, string name, TypeDeclarationSynta public SyntaxBase Value { get; } - public TypeSymbol UnwrapType() => Type is TypeType typeRef ? typeRef.Unwrapped : Type; - public override void Accept(SymbolVisitor visitor) => visitor.VisitTypeAliasSymbol(this); public override SymbolKind Kind => SymbolKind.TypeAlias; diff --git a/src/Bicep.Core/TypeSystem/ArmTemplateTypeLoader.cs b/src/Bicep.Core/TypeSystem/ArmTemplateTypeLoader.cs index cc4077f2af2..12cccb1a2d1 100644 --- a/src/Bicep.Core/TypeSystem/ArmTemplateTypeLoader.cs +++ b/src/Bicep.Core/TypeSystem/ArmTemplateTypeLoader.cs @@ -11,7 +11,9 @@ using Azure.Deployments.Templates.Engines; using Azure.Deployments.Templates.Exceptions; using Bicep.Core.Diagnostics; +using Bicep.Core.Resources; using Bicep.Core.TypeSystem.Types; +using Microsoft.WindowsAzure.ResourceStack.Common.Collections; using Microsoft.WindowsAzure.ResourceStack.Common.Extensions; using Newtonsoft.Json.Linq; @@ -25,6 +27,11 @@ public static TypeSymbol ToTypeSymbol(SchemaValidationContext context, ITemplate { var resolved = TemplateEngine.ResolveSchemaReferences(context, armTemplateSchemaNode); + if (TryGetResourceDerivedType(context, resolved, flags) is TypeSymbol resourceDerivedType) + { + return resourceDerivedType; + } + if (resolved.Type.Value == TemplateParameterType.SecureString || resolved.Type.Value == TemplateParameterType.SecureObject) { flags = TypeSymbolValidationFlags.IsSecure | flags; @@ -55,6 +62,19 @@ TemplateParameterType.Object or } } + private static UnboundResourceDerivedType? TryGetResourceDerivedType(SchemaValidationContext context, ITemplateSchemaNode schemaNode, TypeSymbolValidationFlags flags) + { + if (schemaNode.Metadata?.Value is JObject metadataObject && + metadataObject.TryGetValue(LanguageConstants.MetadataResourceDerivedTypePropertyName, out var resourceType) && + resourceType is JValue { Value: string resourceTypeString } && + ResourceTypeReference.TryParse(resourceTypeString) is ResourceTypeReference resourceTypeReference) + { + return new UnboundResourceDerivedType(resourceTypeReference, ToTypeSymbol(context, new SansMetadata(schemaNode), flags)); + } + + return null; + } + private static TypeSymbol GetStringType(ITemplateSchemaNode schemaNode, TypeSymbolValidationFlags flags) { if (schemaNode.AllowedValues?.Value is JArray jArray) @@ -198,14 +218,21 @@ private static TypeSymbol GetObjectType(SchemaValidationContext context, ITempla if (schemaNode.Discriminator is { } discriminator) { - var variants = ImmutableArray.CreateRange(discriminator.Mapping.Values - .Select(unresolvedVariant => TemplateEngine.ResolveSchemaReferences(context, unresolvedVariant)) - .Select(variant => GetObjectType( - context: context, - properties: schemaNode.Properties.CoalesceEnumerable().Concat(variant.Properties.CoalesceEnumerable()), - requiredProperties: (schemaNode.Required?.Value ?? Array.Empty()).Concat(variant.Required?.Value ?? Array.Empty()), - additionalProperties: variant.AdditionalProperties ?? schemaNode.AdditionalProperties, - flags))); + var variants = ImmutableArray.CreateBuilder(discriminator.Mapping.Count); + foreach (var mappingEntry in discriminator.Mapping) + { + var variant = TemplateEngine.ResolveSchemaReferences(context, mappingEntry.Value); + variants.Add(TryGetResourceDerivedType(context, variant, flags) is { } resourceDerivedObject + ? new UnboundResourceDerivedPartialObjectType(resourceDerivedObject.TypeReference, + discriminator.PropertyName.Value, + mappingEntry.Key) + : GetObjectType( + context: context, + properties: schemaNode.Properties.CoalesceEnumerable().Concat(variant.Properties.CoalesceEnumerable()), + requiredProperties: (schemaNode.Required?.Value ?? Array.Empty()).Concat(variant.Required?.Value ?? Array.Empty()), + additionalProperties: variant.AdditionalProperties ?? schemaNode.AdditionalProperties, + flags)); + } return new DiscriminatedObjectType(string.Join(" | ", TypeHelper.GetOrderedTypeNames(variants)), flags, discriminator.PropertyName.Value, variants); } @@ -266,4 +293,48 @@ private static TypeSymbol GetObjectType(SchemaValidationContext context, return new ObjectType(nameBuilder.ToString(), flags, propertyList, additionalPropertiesType, additionalPropertiesFlags); } + + private class SansMetadata : ITemplateSchemaNode + { + private readonly ITemplateSchemaNode decorated; + + internal SansMetadata(ITemplateSchemaNode toDecorate) + { + this.decorated = toDecorate; + } + + TemplateGenericProperty? ITemplateSchemaNode.Metadata => null; + + TemplateGenericProperty ITemplateSchemaNode.Type => decorated.Type; + + TemplateGenericProperty ITemplateSchemaNode.Ref => decorated.Ref; + + TemplateGenericProperty ITemplateSchemaNode.AllowedValues => decorated.AllowedValues; + + TemplateGenericProperty ITemplateSchemaNode.Nullable => decorated.Nullable; + + TemplateGenericProperty ITemplateSchemaNode.MinValue => decorated.MinValue; + + TemplateGenericProperty ITemplateSchemaNode.MaxValue => decorated.MaxValue; + + TemplateGenericProperty ITemplateSchemaNode.Pattern => decorated.Pattern; + + TemplateGenericProperty ITemplateSchemaNode.MinLength => decorated.MinLength; + + TemplateGenericProperty ITemplateSchemaNode.MaxLength => decorated.MaxLength; + + TemplateTypeDefinition[] ITemplateSchemaNode.PrefixItems => decorated.PrefixItems; + + TemplateBooleanOrSchemaNode ITemplateSchemaNode.Items => decorated.Items; + + InsensitiveDictionary ITemplateSchemaNode.Properties => decorated.Properties; + + TemplateGenericProperty ITemplateSchemaNode.Required => decorated.Required; + + TemplateBooleanOrSchemaNode ITemplateSchemaNode.AdditionalProperties => decorated.AdditionalProperties; + + TemplateGenericProperty ITemplateSchemaNode.Sealed => decorated.Sealed; + + DiscriminatorConstraintDefinition ITemplateSchemaNode.Discriminator => decorated.Discriminator; + } } diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 7679fd84a35..33c9f25d4db 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -12,13 +12,14 @@ using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.Features; +using Bicep.Core.Intermediate; using Bicep.Core.Navigation; using Bicep.Core.Parsing; -using Bicep.Core.Resources; using Bicep.Core.Semantics; using Bicep.Core.Semantics.Namespaces; using Bicep.Core.Syntax; using Bicep.Core.TypeSystem.Types; +using Bicep.Core.Utils; namespace Bicep.Core.TypeSystem { @@ -28,15 +29,18 @@ public class DeclaredTypeManager // processed nodes found not to have a declared type will have a null value private readonly ConcurrentDictionary declaredTypes = new(); private readonly ConcurrentDictionary userDefinedTypeReferences = new(); + private readonly ConcurrentDictionary> reifiedTypes = new(); private readonly ITypeManager typeManager; private readonly IBinder binder; private readonly IFeatureProvider features; + private readonly ResourceDerivedTypeBinder resourceDerivedTypeBinder; public DeclaredTypeManager(TypeManager typeManager, IBinder binder, IFeatureProvider features) { this.typeManager = typeManager; this.binder = binder; this.features = features; + this.resourceDerivedTypeBinder = new(binder, new SimpleDiagnosticWriter(), TextSpan.TextDocumentStart); } public DeclaredTypeAssignment? GetDeclaredTypeAssignment(SyntaxBase syntax) => @@ -44,6 +48,9 @@ public DeclaredTypeManager(TypeManager typeManager, IBinder binder, IFeatureProv public TypeSymbol? GetDeclaredType(SyntaxBase syntax) => this.GetDeclaredTypeAssignment(syntax)?.Reference.Type; + public TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntax syntax) + => GetReifiedTypeResult(syntax).TryUnwrap(); + private DeclaredTypeAssignment? GetTypeAssignment(SyntaxBase syntax) { RuntimeHelpers.EnsureSufficientExecutionStack(); @@ -454,6 +461,7 @@ private ITypeReference GetTypeFromTypeSyntax(SyntaxBase syntax, bool allowNamesp SkippedTriviaSyntax => LanguageConstants.Any, ResourceTypeSyntax resource => GetDeclaredType(resource), VariableAccessSyntax typeRef => ConvertTypeExpressionToType(typeRef, allowNamespaceReferences), + ParameterizedTypeInstantiationSyntax parameterizedTypeInvocation => ConvertTypeExpressionToType(parameterizedTypeInvocation), ArrayTypeSyntax array => GetDeclaredTypeAssignment(array)?.Reference, ObjectTypeSyntax @object => GetDeclaredType(@object), TupleTypeSyntax tuple => GetDeclaredType(tuple), @@ -512,8 +520,8 @@ private ITypeReference ConvertTypeExpressionToType(VariableAccessSyntax syntax, WildcardImportSymbol wildcardImport when allowNamespaceReferences => wildcardImport.Type, BuiltInNamespaceSymbol or ProviderNamespaceSymbol or WildcardImportSymbol => ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).NamespaceSymbolUsedAsType(syntax.Name.IdentifierName)), - AmbientTypeSymbol ambientType => UnwrapType(ambientType.Type), - ImportedTypeSymbol importedType => UnwrapType(importedType.Type), + AmbientTypeSymbol ambientType => UnwrapType(syntax, ambientType.Type), + ImportedTypeSymbol importedType => UnwrapType(syntax, importedType.Type), TypeAliasSymbol declaredType => TypeRefToType(syntax, declaredType), DeclaredSymbol declaredSymbol => ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).ValueSymbolUsedAsType(declaredSymbol.Name)), _ => ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).SymbolicNameIsNotAType(syntax.Name.IdentifierName, GetValidTypeNames())), @@ -524,6 +532,30 @@ private IEnumerable GetValidTypeNames() => binder.NamespaceResolver.GetK .Concat(binder.FileSymbol.ImportedTypes.Select(i => i.Name)) .Distinct(); + private ITypeReference ConvertTypeExpressionToType(ParameterizedTypeInstantiationSyntax syntax) + => GetReifiedTypeResult(syntax).IsSuccess(out var typeExpression, out var error) + ? typeExpression.ExpressedType + : ErrorType.Create(error); + + private Result GetReifiedTypeResult(ParameterizedTypeInstantiationSyntax syntax) + => reifiedTypes.GetOrAdd(syntax, InstantiateType); + + private Result InstantiateType(ParameterizedTypeInstantiationSyntax syntax) => binder.GetSymbolInfo(syntax) switch + { + AmbientTypeSymbol ambientType => InstantiateType(syntax, ambientType, ambientType.Type), + ImportedTypeSymbol importedType => InstantiateType(syntax, importedType, importedType.Type), + TypeAliasSymbol typeAlias => InstantiateType(syntax, typeAlias, typeAlias.Type), + DeclaredSymbol declaredSymbol => new(DiagnosticBuilder.ForPosition(syntax).ValueSymbolUsedAsType(declaredSymbol.Name)), + _ => new(DiagnosticBuilder.ForPosition(syntax).SymbolicNameIsNotAType(syntax.Name.IdentifierName, GetValidTypeNames())), + }; + + private Result InstantiateType(ParameterizedTypeInstantiationSyntax syntax, Symbol symbol, TypeSymbol symbolType) + => symbolType switch + { + TypeTemplate tt => tt.Instantiate(binder, syntax, syntax.Arguments.Select(typeManager.GetTypeInfo)), + _ => new(DiagnosticBuilder.ForPosition(syntax).TypeIsNotParameterizable(symbol.Name)), + }; + private ITypeReference TypeRefToType(VariableAccessSyntax signifier, TypeAliasSymbol signified) => new DeferredTypeReference(() => { var signifiedType = userDefinedTypeReferences.GetOrAdd(signified, GetUserDefinedTypeType); @@ -840,9 +872,10 @@ private TypeSymbol ConvertTypeExpressionToType(PropertyAccessSyntax syntax) : ErrorType.Empty(); } - private TypeSymbol UnwrapType(TypeSymbol type) => type switch + private TypeSymbol UnwrapType(VariableAccessSyntax syntax, TypeSymbol type) => type switch { TypeType tt => tt.Unwrapped, + TypeTemplate template => ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).TypeRequiresParameterization(template.Name, template.Parameters.Length)), _ => type, }; @@ -1729,6 +1762,8 @@ private TypeSymbol GetDeclaredModuleType(ModuleDeclarationSyntax module) type = TypeHelper.CreateTypeUnion(type, LanguageConstants.Null); } + type = resourceDerivedTypeBinder.BindResourceDerivedTypes(type); + parameters.Add(new TypeProperty(parameter.Name, type, flags, parameter.Description)); } @@ -1741,6 +1776,8 @@ private TypeSymbol GetDeclaredModuleType(ModuleDeclarationSyntax module) type = GetResourceTypeFromString(module.Span, unboundType.TypeReference.FormatName(), ResourceTypeGenerationFlags.ExistingResource, parentResourceType: null); } + type = resourceDerivedTypeBinder.BindResourceDerivedTypes(type); + outputs.Add(new TypeProperty(output.Name, type, TypePropertyFlags.ReadOnly, output.Description)); } @@ -1751,6 +1788,7 @@ private TypeSymbol GetDeclaredModuleType(ModuleDeclarationSyntax module) binder.TargetScope, LanguageConstants.TypeNameModule); } + private TypeSymbol GetDeclaredTestType(TestDeclarationSyntax test) { if (binder.GetSymbolInfo(test) is not TestSymbol testSymbol) @@ -1798,73 +1836,9 @@ private TypeSymbol CreateTestType(IEnumerable paramsProperties, st } private TypeSymbol GetResourceTypeFromString(TextSpan span, string stringContent, ResourceTypeGenerationFlags typeGenerationFlags, ResourceType? parentResourceType) - { - var colonIndex = stringContent.IndexOf(':'); - if (colonIndex > 0) - { - var scheme = stringContent.Substring(0, colonIndex); - var typeString = stringContent.Substring(colonIndex + 1); - - if (binder.NamespaceResolver.TryGetNamespace(scheme) is not { } namespaceType) - { - return ErrorType.Create(DiagnosticBuilder.ForPosition(span).UnknownResourceReferenceScheme(scheme, binder.NamespaceResolver.GetNamespaceNames().OrderBy(x => x, StringComparer.OrdinalIgnoreCase))); - } - - if (parentResourceType is not null && - parentResourceType.DeclaringNamespace != namespaceType) - { - return ErrorType.Create(DiagnosticBuilder.ForPosition(span).ParentResourceInDifferentNamespace(namespaceType.Name, parentResourceType.DeclaringNamespace.Name)); - } - - var (errorType, typeReference) = GetCombinedTypeReference(span, typeGenerationFlags, parentResourceType, typeString); - if (errorType is not null) - { - return errorType; - } - - if (typeReference is null) - { - // this won't happen, because GetCombinedTypeReference will either return non-null errorType, or non-null typeReference. - // there's no great way to enforce this in the type system sadly - https://github.com/dotnet/roslyn/discussions/56962 - throw new InvalidOperationException($"typeReference is null"); - } - - if (namespaceType.ResourceTypeProvider.TryGetDefinedType(namespaceType, typeReference, typeGenerationFlags) is { } definedResource) - { - return definedResource; - } - - if (namespaceType.ResourceTypeProvider.TryGenerateFallbackType(namespaceType, typeReference, typeGenerationFlags) is { } defaultResource) - { - return defaultResource; - } - - return ErrorType.Create(DiagnosticBuilder.ForPosition(span).FailedToFindResourceTypeInNamespace(namespaceType.ProviderName, typeReference.FormatName())); - } - else - { - var (errorType, typeReference) = GetCombinedTypeReference(span, typeGenerationFlags, parentResourceType, stringContent); - if (errorType is not null) - { - return errorType; - } - - if (typeReference is null) - { - // this won't happen, because GetCombinedTypeReference will either return non-null errorType, or non-null typeReference. - // there's no great way to enforce this in the type system sadly - https://github.com/dotnet/roslyn/discussions/56962 - throw new InvalidOperationException($"qualifiedTypeReference is null"); - } - - var resourceTypes = binder.NamespaceResolver.GetMatchingResourceTypes(typeReference, typeGenerationFlags); - return resourceTypes.Length switch - { - 0 => ErrorType.Create(DiagnosticBuilder.ForPosition(span).InvalidResourceType()), - 1 => resourceTypes[0], - _ => ErrorType.Create(DiagnosticBuilder.ForPosition(span).AmbiguousResourceTypeBetweenImports(typeReference.FormatName(), resourceTypes.Select(x => x.DeclaringNamespace.Name))), - }; - } - } + => TypeHelper.GetResourceTypeFromString(binder, stringContent, typeGenerationFlags, parentResourceType).IsSuccess(out var resourceType, out var errorBuilder) + ? resourceType + : ErrorType.Create(errorBuilder(DiagnosticBuilder.ForPosition(span))); private (ResourceTypeGenerationFlags flags, ResourceType? parentResourceType) GetResourceTypeGenerationFlags(ResourceDeclarationSyntax resource) { @@ -1905,35 +1879,5 @@ private TypeSymbol GetResourceTypeFromString(TextSpan span, string stringContent return (flags, parentType as ResourceType); } - - private static (ErrorType? error, ResourceTypeReference? typeReference) GetCombinedTypeReference(TextSpan span, ResourceTypeGenerationFlags flags, ResourceType? parentResourceType, string typeString) - { - if (ResourceTypeReference.TryParse(typeString) is not { } typeReference) - { - return (ErrorType.Create(DiagnosticBuilder.ForPosition(span).InvalidResourceType()), null); - } - - if (!flags.HasFlag(ResourceTypeGenerationFlags.NestedResource)) - { - // this is not a syntactically nested resource - return the type reference as-is - return (null, typeReference); - } - - // we're dealing with a syntactically nested resource here - if (parentResourceType is null) - { - return (ErrorType.Create(DiagnosticBuilder.ForPosition(span).InvalidAncestorResourceType()), null); - } - - if (typeReference.TypeSegments.Length > 1) - { - // OK this resource is the one that's wrong. - return (ErrorType.Create(DiagnosticBuilder.ForPosition(span).InvalidResourceTypeSegment(typeString)), null); - } - - return (null, ResourceTypeReference.Combine( - parentResourceType.TypeReference, - typeReference)); - } } } diff --git a/src/Bicep.Core/TypeSystem/Providers/Az/AzResourceTypeFactory.cs b/src/Bicep.Core/TypeSystem/Providers/Az/AzResourceTypeFactory.cs index 4e3e4e5e5bc..74d457f951d 100644 --- a/src/Bicep.Core/TypeSystem/Providers/Az/AzResourceTypeFactory.cs +++ b/src/Bicep.Core/TypeSystem/Providers/Az/AzResourceTypeFactory.cs @@ -108,11 +108,11 @@ private TypeSymbol ToTypeSymbol(Azure.Bicep.Types.Concrete.TypeBase typeBase, bo case Azure.Bicep.Types.Concrete.NullType: return LanguageConstants.Null; case Azure.Bicep.Types.Concrete.BooleanType: - return LanguageConstants.Bool; + return TypeFactory.CreateBooleanType(GetValidationFlags(isResourceBodyType)); case Azure.Bicep.Types.Concrete.IntegerType @int: - return TypeFactory.CreateIntegerType(@int.MinValue, @int.MaxValue); + return TypeFactory.CreateIntegerType(@int.MinValue, @int.MaxValue, GetValidationFlags(isResourceBodyType)); case Azure.Bicep.Types.Concrete.StringType @string: - return TypeFactory.CreateStringType(@string.MinLength, @string.MaxLength); + return TypeFactory.CreateStringType(@string.MinLength, @string.MaxLength, GetValidationFlags(isResourceBodyType)); case Azure.Bicep.Types.Concrete.BuiltInType builtInType: return builtInType.Kind switch { @@ -144,7 +144,7 @@ private TypeSymbol ToTypeSymbol(Azure.Bicep.Types.Concrete.TypeBase typeBase, bo return TypeHelper.CreateTypeUnion(unionType.Elements.Select(x => GetTypeReference(x))); } case Azure.Bicep.Types.Concrete.StringLiteralType stringLiteralType: - return TypeFactory.CreateStringLiteralType(stringLiteralType.Value); + return TypeFactory.CreateStringLiteralType(stringLiteralType.Value, GetValidationFlags(isResourceBodyType)); case Azure.Bicep.Types.Concrete.DiscriminatedObjectType discriminatedObjectType: { var elementReferences = discriminatedObjectType.Elements.Select(kvp => new DeferredTypeReference(() => ToCombinedType(discriminatedObjectType.BaseProperties, kvp.Key, kvp.Value, isResourceBodyType))); diff --git a/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs new file mode 100644 index 00000000000..b6ed7573631 --- /dev/null +++ b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs @@ -0,0 +1,282 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Numerics; +using Bicep.Core.Diagnostics; +using Bicep.Core.Extensions; +using Bicep.Core.Parsing; +using Bicep.Core.Resources; +using Bicep.Core.Semantics; +using Bicep.Core.Semantics.Namespaces; +using Bicep.Core.Text; +using Bicep.Core.TypeSystem.Types; +using Bicep.Core.Utils; +using Newtonsoft.Json.Linq; + +namespace Bicep.Core.TypeSystem; + +public class ResourceDerivedTypeBinder +{ + private readonly Stack currentlyBinding = new(); + private readonly ConcurrentDictionary boundTypes = new(); + private readonly Stack currentlySearchingForUnboundTypes = new(); + private readonly ConcurrentDictionary containsUnboundTypesCache = new(); + private readonly IBinder binder; + private readonly IDiagnosticWriter diagnostics; + private readonly IPositionable diagnosticTarget; + + public ResourceDerivedTypeBinder(IBinder binder, IDiagnosticWriter diagnostics, IPositionable diagnosticTarget) + { + this.binder = binder; + this.diagnostics = diagnostics; + this.diagnosticTarget = diagnosticTarget; + } + + public TypeSymbol BindResourceDerivedTypes(TypeSymbol unbound) => boundTypes.GetOrAdd(unbound, CalculateTypeBinding); + + private TypeSymbol CalculateTypeBinding(TypeSymbol unbound) + { + if (!ContainsUnboundTypes(unbound)) + { + return unbound; + } + + currentlyBinding.Push(unbound); + + var bound = unbound switch + { + IUnboundResourceDerivedType resourceDerivedType => CalculateTypeBinding(resourceDerivedType), + TupleType tuple => CalculateTypeBinding(tuple), + ArrayType array => CalculateTypeBinding(array), + DiscriminatedObjectType taggedUnion => CalculateTypeBinding(taggedUnion), + ObjectType @object => CalculateTypeBinding(@object), + UnionType union => CalculateTypeBinding(union), + TypeType typeType => CalculateTypeBinding(typeType), + _ when IsPrimitiveType(unbound) => unbound, + _ => throw new UnreachableException($"Unexpected type ({unbound.GetType().FullName}) encountered"), + }; + + currentlyBinding.Pop(); + + return bound; + } + + private TypeSymbol CalculateTypeBinding(IUnboundResourceDerivedType unbound) + { + // TODO support types derived from resources other than the `az` provider. This will require some refactoring of how provider artifacts are restored + var bound = binder.NamespaceResolver.GetMatchingResourceTypes(unbound.TypeReference, ResourceTypeGenerationFlags.None) + .Where(resourceType => LanguageConstants.IdentifierComparer.Equals(resourceType.DeclaringNamespace.ProviderName, AzNamespaceType.BuiltInName)) + .FirstOrDefault(); + + if (bound is null) + { + diagnostics.Write(DiagnosticBuilder.ForPosition(diagnosticTarget).ResourceTypesUnavailable(unbound.TypeReference)); + return unbound.FallbackType; + } + + return bound.Body.Type; + } + + private TupleType CalculateTypeBinding(TupleType unbound) + { + var boundItemTypes = ImmutableArray.CreateBuilder(unbound.Items.Length); + var hasChanges = false; + + for (int i = 0; i < unbound.Items.Length; i++) + { + var unboundItemType = unbound.Items[i].Type; + if (currentlyBinding.Contains(unboundItemType)) + { + boundItemTypes.Add(new DeferredTypeReference(() => boundTypes[unboundItemType])); + hasChanges = hasChanges || ContainsUnboundTypes(unboundItemType);; + } + else + { + boundItemTypes.Add(BindResourceDerivedTypes(unboundItemType)); + hasChanges = hasChanges || !ReferenceEquals(boundItemTypes[i].Type, unboundItemType); + } + } + + return hasChanges + ? new(unbound.Name, boundItemTypes.ToImmutable(), unbound.ValidationFlags) + : unbound; + } + + private ArrayType CalculateTypeBinding(ArrayType unbound) + { + var unboundItemType = unbound.Item.Type; + if (currentlyBinding.Contains(unboundItemType)) + { + return new TypedArrayType(unbound.Name, + new DeferredTypeReference(() => boundTypes[unboundItemType]), + unbound.ValidationFlags, + unbound.MinLength, + unbound.MaxLength); + } + + var boundItemType = BindResourceDerivedTypes(unbound.Item.Type); + + return ReferenceEquals(boundItemType, unbound.Item.Type) + ? unbound + : new TypedArrayType(unbound.Name, boundItemType, unbound.ValidationFlags, unbound.MinLength, unbound.MaxLength); + } + + private DiscriminatedObjectType CalculateTypeBinding(DiscriminatedObjectType unbound) + { + var boundUnionMembers = ImmutableArray.CreateBuilder(unbound.UnionMembersByKey.Count); + var hasChanges = false; + + foreach (var unboundMember in unbound.UnionMembersByKey.Values) + { + var boundMember = BindResourceDerivedTypes(unboundMember.Type); + hasChanges = hasChanges || !ReferenceEquals(boundMember, unboundMember.Type); + boundUnionMembers.Add(boundMember); + } + + return hasChanges + ? new(unbound.Name, unbound.ValidationFlags, unbound.DiscriminatorKey, boundUnionMembers.ToImmutable()) + : unbound; + } + + private ObjectType CalculateTypeBinding(ObjectType unbound) + { + var boundProperties = ImmutableArray.CreateBuilder(unbound.Properties.Count); + var hasChanges = false; + + foreach (var unboundProperty in unbound.Properties.Values) + { + var unboundPropertyType = unboundProperty.TypeReference.Type; + if (currentlyBinding.Contains(unboundPropertyType)) + { + boundProperties.Add(new(unboundProperty.Name, + new DeferredTypeReference(() => boundTypes[unboundPropertyType]), + unboundProperty.Flags, + unboundProperty.Description)); + hasChanges = hasChanges || ContainsUnboundTypes(unboundPropertyType); + } + else + { + var boundPropertyType = BindResourceDerivedTypes(unboundPropertyType); + boundProperties.Add(new(unboundProperty.Name, + boundPropertyType, + unboundProperty.Flags, + unboundProperty.Description)); + hasChanges = hasChanges || !ReferenceEquals(unboundPropertyType, boundPropertyType); + } + } + + var addlPropertiesType = unbound.AdditionalPropertiesType; + if (addlPropertiesType is not null) + { + var boundAddlPropertiesType = BindResourceDerivedTypes(addlPropertiesType.Type); + hasChanges = hasChanges || !ReferenceEquals(addlPropertiesType.Type, boundAddlPropertiesType); + addlPropertiesType = boundAddlPropertiesType; + } + + return hasChanges + ? new(unbound.Name, unbound.ValidationFlags, boundProperties.ToImmutable(), addlPropertiesType, unbound.AdditionalPropertiesFlags) + : unbound; + } + + private UnionType CalculateTypeBinding(UnionType unbound) + { + var boundMembers = ImmutableArray.CreateBuilder(unbound.Members.Length); + var hasChanges = false; + + for (int i = 0; i < unbound.Members.Length; i++) + { + var unboundMemberType = unbound.Members[i].Type; + if (currentlyBinding.Contains(unboundMemberType)) + { + boundMembers.Add(new DeferredTypeReference(() => boundTypes[unboundMemberType])); + hasChanges = hasChanges || ContainsUnboundTypes(unboundMemberType);; + } + else + { + boundMembers.Add(BindResourceDerivedTypes(unboundMemberType)); + hasChanges = hasChanges || !ReferenceEquals(boundMembers[i].Type, unboundMemberType); + } + } + + return hasChanges + ? new(unbound.Name, boundMembers.ToImmutable()) + : unbound; + } + + private TypeType CalculateTypeBinding(TypeType unbound) + { + var boundUnwrapped = BindResourceDerivedTypes(unbound.Unwrapped); + + return ReferenceEquals(boundUnwrapped, unbound.Unwrapped) + ? unbound + : new(boundUnwrapped); + } + + private bool ContainsUnboundTypes(TypeSymbol type) + { + if (currentlySearchingForUnboundTypes.Contains(type)) + { + // types may be recursive, so cut out early if we hit a recursion point + return false; + } + + return containsUnboundTypesCache.GetOrAdd(type, type => + { + currentlySearchingForUnboundTypes.Push(type); + + var containsUnboundTypes = type switch + { + IUnboundResourceDerivedType => true, + TupleType tuple => ContainsUnboundTypes(tuple), + ArrayType array => ContainsUnboundTypes(array), + DiscriminatedObjectType taggedUnion => ContainsUnboundTypes(taggedUnion), + ObjectType @object => ContainsUnboundTypes(@object), + UnionType union => ContainsUnboundTypes(union), + TypeType typeType => ContainsUnboundTypes(typeType), + _ when IsPrimitiveType(type) => false, + _ => throw new UnreachableException($"Unexpected type ({type.GetType().FullName}) encountered"), + }; + + currentlySearchingForUnboundTypes.Pop(); + + return containsUnboundTypes; + }); + } + + private bool ContainsUnboundTypes(TupleType tuple) + => tuple.Items.Any(item => ContainsUnboundTypes(item.Type)); + + private bool ContainsUnboundTypes(ArrayType array) + => ContainsUnboundTypes(array.Item.Type); + + private bool ContainsUnboundTypes(DiscriminatedObjectType discriminatedObject) + => discriminatedObject.UnionMembersByKey.Values.Any(variant => ContainsUnboundTypes(variant as TypeSymbol)); + + private bool ContainsUnboundTypes(ObjectType objectType) + => objectType.Properties.Values.Any(property => ContainsUnboundTypes(property.TypeReference.Type)) || + (objectType.AdditionalPropertiesType?.Type is TypeSymbol addlPropertiesType && ContainsUnboundTypes(addlPropertiesType)); + + private bool ContainsUnboundTypes(UnionType union) + => union.Members.Any(member => ContainsUnboundTypes(member.Type)); + + private bool ContainsUnboundTypes(TypeType typeType) + => ContainsUnboundTypes(typeType.Unwrapped); + + private static bool IsPrimitiveType(TypeSymbol type) => type is AnyType or + BooleanLiteralType or + BooleanType or + ErrorType or + IntegerLiteralType or + IntegerType or + NullType or + StringLiteralType or + StringType; +} diff --git a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs index 8add20e6367..94da84d4e2a 100644 --- a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs +++ b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs @@ -726,6 +726,9 @@ public override void VisitResourceTypeSyntax(ResourceTypeSyntax syntax) return declaredType; }); + public override void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) + => AssignType(syntax, () => typeManager.GetTypeInfo(syntax.Expression)); + private TypeSymbol GetDeclaredTypeAndValidateDecorators(DecorableSyntax targetSyntax, SyntaxBase typeSyntax, IDiagnosticWriter diagnostics) { var declaredType = typeManager.GetDeclaredType(targetSyntax); @@ -970,6 +973,7 @@ public override void VisitWildcardImportSyntax(WildcardImportSyntax syntax) List nsProperties = new(); List nsFunctions = new(); + ResourceDerivedTypeBinder resourceDerivedTypeBinder = new(binder, diagnostics, syntax); foreach (var export in importedModel.Exports.Values) { if (export is ExportedFunctionMetadata exportedFunction) @@ -978,7 +982,10 @@ public override void VisitWildcardImportSyntax(WildcardImportSyntax syntax) } else { - nsProperties.Add(new(export.Name, export.TypeReference, TypePropertyFlags.ReadOnly | TypePropertyFlags.Required, export.Description)); + nsProperties.Add(new(export.Name, + resourceDerivedTypeBinder.BindResourceDerivedTypes(export.TypeReference.Type), + TypePropertyFlags.ReadOnly | TypePropertyFlags.Required, + export.Description)); } } @@ -1019,7 +1026,9 @@ public override void VisitImportedSymbolsListItemSyntax(ImportedSymbolsListItemS return ErrorType.Empty(); } - return exported.TypeReference; + ResourceDerivedTypeBinder resourceDerivedTypeBinder = new(binder, diagnostics, syntax); + + return resourceDerivedTypeBinder.BindResourceDerivedTypes(exported.TypeReference.Type); }); public override void VisitBooleanLiteralSyntax(BooleanLiteralSyntax syntax) diff --git a/src/Bicep.Core/TypeSystem/TypeHelper.cs b/src/Bicep.Core/TypeSystem/TypeHelper.cs index 1498a79a4f7..1e89b79df71 100644 --- a/src/Bicep.Core/TypeSystem/TypeHelper.cs +++ b/src/Bicep.Core/TypeSystem/TypeHelper.cs @@ -11,6 +11,8 @@ using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.Parsing; +using Bicep.Core.Resources; +using Bicep.Core.Semantics; using Bicep.Core.Text; using Bicep.Core.TypeSystem.Types; using Newtonsoft.Json.Linq; @@ -391,6 +393,57 @@ _ when ArmFunctionReturnTypeEvaluator.TryEvaluate("string", out _, type.AsEnumer _ => (0, null), }; + public static ResultWithDiagnostic GetResourceTypeFromString(IBinder binder, string stringContent, ResourceTypeGenerationFlags typeGenerationFlags, ResourceType? parentResourceType) + { + var colonIndex = stringContent.IndexOf(':'); + if (colonIndex > 0) + { + var scheme = stringContent[..colonIndex]; + var typeString = stringContent[(colonIndex + 1)..]; + + if (binder.NamespaceResolver.TryGetNamespace(scheme) is not { } namespaceType) + { + return new(span => span.UnknownResourceReferenceScheme(scheme, binder.NamespaceResolver.GetNamespaceNames().OrderBy(x => x, StringComparer.OrdinalIgnoreCase))); + } + + if (parentResourceType is not null && + parentResourceType.DeclaringNamespace != namespaceType) + { + return new(span => span.ParentResourceInDifferentNamespace(namespaceType.Name, parentResourceType.DeclaringNamespace.Name)); + } + + if (!GetCombinedTypeReference(typeGenerationFlags, parentResourceType, typeString).IsSuccess(out var typeReference, out var builder)) + { + return new(builder); + } + + if (namespaceType.ResourceTypeProvider.TryGetDefinedType(namespaceType, typeReference, typeGenerationFlags) is { } definedResource) + { + return new(definedResource); + } + + if (namespaceType.ResourceTypeProvider.TryGenerateFallbackType(namespaceType, typeReference, typeGenerationFlags) is { } defaultResource) + { + return new(defaultResource); + } + + return new(span => span.FailedToFindResourceTypeInNamespace(namespaceType.ProviderName, typeReference.FormatName())); + } + + if (!GetCombinedTypeReference(typeGenerationFlags, parentResourceType, stringContent).IsSuccess(out var typeRef, out var errorBuilder)) + { + return new(errorBuilder); + } + + var resourceTypes = binder.NamespaceResolver.GetMatchingResourceTypes(typeRef, typeGenerationFlags); + return resourceTypes.Length switch + { + 0 => new(span => span.InvalidResourceType()), + 1 => new(resourceTypes[0]), + _ => new(span => span.AmbiguousResourceTypeBetweenImports(typeRef.FormatName(), resourceTypes.Select(x => x.DeclaringNamespace.Name))), + }; + } + private static ImmutableArray NormalizeTypeList(IEnumerable unionMembers) { HashSet distinctMembers = new(); @@ -434,5 +487,33 @@ public static bool SatisfiesCondition(TypeSymbol typeSymbol, Func unionType.Members.All(t => conditionFunc(t.Type)), _ => conditionFunc(typeSymbol), }; + + private static ResultWithDiagnostic GetCombinedTypeReference(ResourceTypeGenerationFlags flags, ResourceType? parentResourceType, string typeString) + { + if (ResourceTypeReference.TryParse(typeString) is not { } typeReference) + { + return new(span => span.InvalidResourceType()); + } + + if (!flags.HasFlag(ResourceTypeGenerationFlags.NestedResource)) + { + // this is not a syntactically nested resource - return the type reference as-is + return new(typeReference); + } + + // we're dealing with a syntactically nested resource here + if (parentResourceType is null) + { + return new(span => span.InvalidAncestorResourceType()); + } + + if (typeReference.TypeSegments.Length > 1) + { + // OK this resource is the one that's wrong. + return new(span => span.InvalidResourceTypeSegment(typeString)); + } + + return new(ResourceTypeReference.Combine(parentResourceType.TypeReference, typeReference)); + } } } diff --git a/src/Bicep.Core/TypeSystem/TypeKind.cs b/src/Bicep.Core/TypeSystem/TypeKind.cs index 4e58585a847..e1150d1c947 100644 --- a/src/Bicep.Core/TypeSystem/TypeKind.cs +++ b/src/Bicep.Core/TypeSystem/TypeKind.cs @@ -93,5 +93,10 @@ public enum TypeKind /// A reference to a type symbol /// TypeReference, + + /// + /// A kind that is not yet known because the resource type from which it is derived has not yet been resolved. + /// + UnboundResourceDerivedType, } } diff --git a/src/Bicep.Core/TypeSystem/TypeManager.cs b/src/Bicep.Core/TypeSystem/TypeManager.cs index ba898fb538e..f1005cdad38 100644 --- a/src/Bicep.Core/TypeSystem/TypeManager.cs +++ b/src/Bicep.Core/TypeSystem/TypeManager.cs @@ -45,5 +45,7 @@ public IEnumerable GetAllDiagnostics() public Expression? GetMatchedFunctionResultValue(FunctionCallSyntaxBase syntax) => typeAssignmentVisitor.GetMatchedFunctionResultValue(syntax); + public TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntax syntax) + => declaredTypeManager.TryGetReifiedType(syntax); } } diff --git a/src/Bicep.Core/TypeSystem/Types/IUnboundResourceDerivedType.cs b/src/Bicep.Core/TypeSystem/Types/IUnboundResourceDerivedType.cs new file mode 100644 index 00000000000..b647e5d0c32 --- /dev/null +++ b/src/Bicep.Core/TypeSystem/Types/IUnboundResourceDerivedType.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Resources; + +namespace Bicep.Core.TypeSystem.Types; + +/// +/// UnboundResourceDerivedType represents a type expressed via a reference to a resource type or partial body thereof +/// (e.g., Microsoft.KeyVault/vaults@2022-07-01#/properties/accessPolicies/*). This type is "unbound" because it was +/// used in the type of a parameter, output, or exported type definition in an ARM JSON template and must be bound +/// to a concrete resource definition based on the configured providers of the consuming Bicep module. +/// +public interface IUnboundResourceDerivedType +{ + // TODO This type needs to capture: + // - A provider identifier (built-in name or OCI reference) + // - A type reference + // - A list of properties to traverse + // - An ARM primitive type + + public ResourceTypeReference TypeReference { get; } + + public TypeSymbol FallbackType { get; } +} diff --git a/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs b/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs new file mode 100644 index 00000000000..9a9785c95f8 --- /dev/null +++ b/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using Bicep.Core.Diagnostics; + +namespace Bicep.Core.TypeSystem.Types; + +public class TypeParameter +{ + public TypeParameter(string name) + { + Name = name; + } + + public string Name { get; } + + public override string ToString() => Name; +} diff --git a/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs new file mode 100644 index 00000000000..065e5395ab0 --- /dev/null +++ b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Bicep.Core.Diagnostics; +using Bicep.Core.Intermediate; +using Bicep.Core.Parsing; +using Bicep.Core.Semantics; +using Bicep.Core.Syntax; +using Bicep.Core.Utils; + +namespace Bicep.Core.TypeSystem.Types; + +/// +/// A type that must be parameterized in order to be used. +/// +public class TypeTemplate : TypeSymbol +{ + public delegate Result InstantiatorDelegate( + IBinder binder, + ParameterizedTypeInstantiationSyntax instantiationSyntax, + ImmutableArray argumentTypes); + + private readonly InstantiatorDelegate instantiator; + + public TypeTemplate(string name, ImmutableArray parameters, InstantiatorDelegate instantiator) + : base($"Type<{name}<{string.Join(", ", parameters)}>>") + { + Debug.Assert(!parameters.IsEmpty, "Parameterized types must accept at least one argument."); + + Parameters = parameters; + this.instantiator = instantiator; + } + + public ImmutableArray Parameters { get; } + + public override TypeSymbolValidationFlags ValidationFlags => TypeSymbolValidationFlags.PreventAssignment; + + public override TypeKind TypeKind => TypeKind.TypeReference; + + public override IEnumerable GetDiagnostics() => ImmutableArray.Empty; + + public Result Instantiate(IBinder binder, ParameterizedTypeInstantiationSyntax syntax, IEnumerable argumentTypes) + { + var argTypesArray = argumentTypes.ToImmutableArray(); + + if (argTypesArray.Length != Parameters.Length) + { + return new(DiagnosticBuilder.ForPosition(TextSpan.Between(syntax.OpenChevron, syntax.CloseChevron)) + .ArgumentCountMismatch(argTypesArray.Length, Parameters.Length, Parameters.Length)); + } + + return instantiator.Invoke(binder, syntax, argTypesArray); + } +} diff --git a/src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedPartialObjectType.cs b/src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedPartialObjectType.cs new file mode 100644 index 00000000000..096e6f84967 --- /dev/null +++ b/src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedPartialObjectType.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Extensions; +using Bicep.Core.Resources; + +namespace Bicep.Core.TypeSystem.Types; + +/// +/// An IUnboundResourceDerivedType to use as a branch of a DiscriminatedObjectType +/// +public class UnboundResourceDerivedPartialObjectType : ObjectType, IUnboundResourceDerivedType +{ + public UnboundResourceDerivedPartialObjectType(ResourceTypeReference typeReference, string discriminatorName, string discriminatorValue) + : base(typeReference.FormatType(), + TypeSymbolValidationFlags.Default, + new TypeProperty(discriminatorName, TypeFactory.CreateStringLiteralType(discriminatorValue)).AsEnumerable(), + LanguageConstants.Any, + TypePropertyFlags.FallbackProperty) + { + TypeReference = typeReference; + } + + public ResourceTypeReference TypeReference { get; } + + public TypeSymbol FallbackType => this; + + public override TypeKind TypeKind => TypeKind.UnboundResourceDerivedType; +} diff --git a/src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedType.cs b/src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedType.cs new file mode 100644 index 00000000000..10e545524f9 --- /dev/null +++ b/src/Bicep.Core/TypeSystem/Types/UnboundResourceDerivedType.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Resources; + +namespace Bicep.Core.TypeSystem.Types; + +public class UnboundResourceDerivedType : TypeSymbol, IUnboundResourceDerivedType +{ + public UnboundResourceDerivedType(ResourceTypeReference typeReference, TypeSymbol fallbackType) + : base(typeReference.FormatType()) + { + TypeReference = typeReference; + FallbackType = fallbackType; + } + + public ResourceTypeReference TypeReference { get; } + + public TypeSymbol FallbackType { get; } + + public override TypeKind TypeKind => TypeKind.UnboundResourceDerivedType; +} From 0fef927f3840b6dad651d0e6d75870cdd06c751f Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 1 Dec 2023 10:13:37 -0500 Subject: [PATCH 04/20] Fix failing tests --- .../TypeSystem/DeclaredTypeManager.cs | 14 +++++--- .../TypeSystem/ResourceDerivedTypeBinder.cs | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 33c9f25d4db..ebc034dd48f 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -1753,6 +1753,10 @@ private TypeSymbol GetDeclaredModuleType(ModuleDeclarationSyntax module) type = new ResourceParameterType(resourceType.DeclaringNamespace, unboundType.TypeReference); } } + else + { + type = resourceDerivedTypeBinder.BindResourceDerivedTypes(type); + } var flags = parameter.IsRequired ? TypePropertyFlags.Required | TypePropertyFlags.WriteOnly : TypePropertyFlags.WriteOnly; @@ -1762,8 +1766,6 @@ private TypeSymbol GetDeclaredModuleType(ModuleDeclarationSyntax module) type = TypeHelper.CreateTypeUnion(type, LanguageConstants.Null); } - type = resourceDerivedTypeBinder.BindResourceDerivedTypes(type); - parameters.Add(new TypeProperty(parameter.Name, type, flags, parameter.Description)); } @@ -1775,8 +1777,10 @@ private TypeSymbol GetDeclaredModuleType(ModuleDeclarationSyntax module) { type = GetResourceTypeFromString(module.Span, unboundType.TypeReference.FormatName(), ResourceTypeGenerationFlags.ExistingResource, parentResourceType: null); } - - type = resourceDerivedTypeBinder.BindResourceDerivedTypes(type); + else + { + type = resourceDerivedTypeBinder.BindResourceDerivedTypes(type); + } outputs.Add(new TypeProperty(output.Name, type, TypePropertyFlags.ReadOnly, output.Description)); } @@ -1788,7 +1792,7 @@ private TypeSymbol GetDeclaredModuleType(ModuleDeclarationSyntax module) binder.TargetScope, LanguageConstants.TypeNameModule); } - + private TypeSymbol GetDeclaredTestType(TestDeclarationSyntax test) { if (binder.GetSymbolInfo(test) is not TestSymbol testSymbol) diff --git a/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs index b6ed7573631..90482b795ad 100644 --- a/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs +++ b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs @@ -60,6 +60,7 @@ private TypeSymbol CalculateTypeBinding(TypeSymbol unbound) ObjectType @object => CalculateTypeBinding(@object), UnionType union => CalculateTypeBinding(union), TypeType typeType => CalculateTypeBinding(typeType), + LambdaType lambda => CalculateTypeBinding(lambda), _ when IsPrimitiveType(unbound) => unbound, _ => throw new UnreachableException($"Unexpected type ({unbound.GetType().FullName}) encountered"), }; @@ -220,6 +221,34 @@ private TypeType CalculateTypeBinding(TypeType unbound) : new(boundUnwrapped); } + private LambdaType CalculateTypeBinding(LambdaType unbound) + { + var boundParameterTypes = ImmutableArray.CreateBuilder(unbound.ArgumentTypes.Length); + var hasChanges = false; + + for (int i = 0; i < unbound.ArgumentTypes.Length; i++) + { + var unboundParameterType = unbound.ArgumentTypes[i].Type; + if (currentlyBinding.Contains(unboundParameterType)) + { + boundParameterTypes.Add(new DeferredTypeReference(() => boundTypes[unboundParameterType])); + hasChanges = hasChanges || ContainsUnboundTypes(unboundParameterType); + } + else + { + boundParameterTypes.Add(BindResourceDerivedTypes(unboundParameterType)); + hasChanges = hasChanges || !ReferenceEquals(boundParameterTypes[i].Type, unboundParameterType); + } + } + + var boundReturnType = BindResourceDerivedTypes(unbound.ReturnType.Type); + hasChanges = hasChanges || !ReferenceEquals(boundReturnType, unbound.ReturnType.Type); + + return hasChanges + ? new(boundParameterTypes.ToImmutable(), boundReturnType) + : unbound; + } + private bool ContainsUnboundTypes(TypeSymbol type) { if (currentlySearchingForUnboundTypes.Contains(type)) @@ -241,6 +270,7 @@ private bool ContainsUnboundTypes(TypeSymbol type) ObjectType @object => ContainsUnboundTypes(@object), UnionType union => ContainsUnboundTypes(union), TypeType typeType => ContainsUnboundTypes(typeType), + LambdaType lambda => ContainsUnboundTypes(lambda), _ when IsPrimitiveType(type) => false, _ => throw new UnreachableException($"Unexpected type ({type.GetType().FullName}) encountered"), }; @@ -270,6 +300,10 @@ private bool ContainsUnboundTypes(UnionType union) private bool ContainsUnboundTypes(TypeType typeType) => ContainsUnboundTypes(typeType.Unwrapped); + private bool ContainsUnboundTypes(LambdaType lambda) + => lambda.ArgumentTypes.Any(argType => ContainsUnboundTypes(argType.Type)) || + ContainsUnboundTypes(lambda.ReturnType.Type); + private static bool IsPrimitiveType(TypeSymbol type) => type is AnyType or BooleanLiteralType or BooleanType or From 2f84542ced3922fd8a2a63a04fcdfb364371121e Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 1 Dec 2023 10:17:34 -0500 Subject: [PATCH 05/20] Move resource type behind feature flag --- .../CompileTimeImportTests.cs | 102 +++++++++++++++++- .../Features/FeatureProviderOverrides.cs | 3 + .../Features/OverriddenFeatureProvider.cs | 2 + .../ResourceDerivedTypeBinderTests.cs | 9 +- .../ExperimentalFeaturesEnabled.cs | 3 +- .../Diagnostics/DiagnosticBuilder.cs | 7 +- src/Bicep.Core/Features/FeatureProvider.cs | 2 + src/Bicep.Core/Features/IFeatureProvider.cs | 3 + .../Namespaces/SystemNamespaceType.cs | 39 +++---- .../TypeSystem/DeclaredTypeManager.cs | 2 +- .../TypeSystem/ResourceDerivedTypeBinder.cs | 23 +--- .../ResourceDerivedTypeDiagnosticReporter.cs | 76 +++++++++++++ .../TypeSystem/TypeAssignmentVisitor.cs | 31 ++++-- 13 files changed, 244 insertions(+), 58 deletions(-) create mode 100644 src/Bicep.Core/TypeSystem/ResourceDerivedTypeDiagnosticReporter.cs diff --git a/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs b/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs index 5852d27b0b4..361c570576e 100644 --- a/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs +++ b/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs @@ -1938,7 +1938,7 @@ func isWindows(hostingPlanName string) string => contains('-windows-', hostingPl [TestMethod] public void Resource_derived_types_are_bound_when_imported_from_ARM_JSON_models() { - var result = CompilationHelper.Compile(ServicesWithCompileTimeTypeImports, + var result = CompilationHelper.Compile(new ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true, CompileTimeImportsEnabled: true)), ("main.bicep", """ import {foo} from 'mod.json' @@ -1988,4 +1988,104 @@ public void Resource_derived_types_are_bound_when_imported_from_ARM_JSON_models( ("BCP037", DiagnosticLevel.Warning, """The property "unknownProperty" is not allowed on objects of type "StorageAccountPropertiesCreateParametersOrStorageAccountProperties". Permissible properties include "accessTier", "allowBlobPublicAccess", "allowCrossTenantReplication", "allowedCopyScope", "allowSharedKeyAccess", "azureFilesIdentityBasedAuthentication", "customDomain", "defaultToOAuthAuthentication", "dnsEndpointType", "encryption", "immutableStorageWithVersioning", "isHnsEnabled", "isLocalUserEnabled", "isNfsV3Enabled", "isSftpEnabled", "keyPolicy", "largeFileSharesState", "minimumTlsVersion", "networkAcls", "publicNetworkAccess", "routingPreference", "sasPolicy", "supportsHttpsTrafficOnly"."""), }); } + + [TestMethod] + public void Resource_derived_typed_compile_time_imports_raise_diagnostic_when_imported_from_ARM_JSON_models_without_feature_flag_set() + { + var result = CompilationHelper.Compile(ServicesWithCompileTimeTypeImports, + ("main.bicep", """ + import {foo} from 'mod.json' + + param location string = resourceGroup().location + param fooParam foo = { + bar: { + name: 'acct' + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + } + } + + output fooOutput foo = fooParam + """), + ("mod.json", $$""" + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "definitions": { + "foo": { + "metadata": { + "{{LanguageConstants.MetadataExportedPropertyName}}": true + }, + "type": "object", + "additionalProperties": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + }, + "resources": [] + } + """)); + + result.Should().HaveDiagnostics(new[] + { + ("BCP385", DiagnosticLevel.Error, """Using resource-derived types requires enabling EXPERIMENTAL feature "ResourceDerivedTypes"."""), + }); + } + + [TestMethod] + public void Resource_derived_typed_compile_time_imports_raise_diagnostic_when_imported_from_ARM_JSON_models_with_unrecognized_resource() + { + var result = CompilationHelper.Compile(new ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true, CompileTimeImportsEnabled: true)), + ("main.bicep", """ + import {foo} from 'mod.json' + + param location string = resourceGroup().location + param fooParam foo = { + bar: { + name: 'acct' + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + } + } + + output fooOutput foo = fooParam + """), + ("mod.json", $$""" + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "definitions": { + "foo": { + "metadata": { + "{{LanguageConstants.MetadataExportedPropertyName}}": true + }, + "type": "object", + "additionalProperties": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Foo/bars@2022-09-01" + } + } + } + }, + "resources": [] + } + """)); + + result.Should().HaveDiagnostics(new[] + { + ("BCP081", DiagnosticLevel.Warning, """Resource type "Microsoft.Foo/bars@2022-09-01" does not have types available."""), + }); + } } diff --git a/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs b/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs index d437045e971..749fc6ec625 100644 --- a/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs +++ b/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs @@ -23,6 +23,7 @@ public record FeatureProviderOverrides( bool? CompileTimeImportsEnabled = default, bool? MicrosoftGraphPreviewEnabled = default, bool? PublishSourceEnabled = default, + bool? ResourceDerivedTypesEnabled = default, string? AssemblyVersion = BicepTestConstants.DevAssemblyFileVersion) { public FeatureProviderOverrides( @@ -42,6 +43,7 @@ public FeatureProviderOverrides( bool? CompileTimeImportsEnabled = default, bool? MicrosoftGraphPreviewEnabled = default, bool? PublishSourceEnabled = default, + bool? ResourceDerivedTypesEnabled = default, string? AssemblyVersion = BicepTestConstants.DevAssemblyFileVersion ) : this( FileHelper.GetCacheRootPath(testContext), @@ -60,6 +62,7 @@ public FeatureProviderOverrides( CompileTimeImportsEnabled, MicrosoftGraphPreviewEnabled, PublishSourceEnabled, + ResourceDerivedTypesEnabled, AssemblyVersion) { } } diff --git a/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs b/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs index 853e9e276db..aaf2658692f 100644 --- a/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs +++ b/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs @@ -45,4 +45,6 @@ public OverriddenFeatureProvider(IFeatureProvider features, FeatureProviderOverr public bool MicrosoftGraphPreviewEnabled => overrides.MicrosoftGraphPreviewEnabled ?? features.MicrosoftGraphPreviewEnabled; public bool PublishSourceEnabled => overrides.PublishSourceEnabled ?? features.PublishSourceEnabled; + + public bool ResourceDerivedTypesEnabled => overrides.ResourceDerivedTypesEnabled ?? features.ResourceDerivedTypesEnabled; } diff --git a/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs b/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs index f050973c54d..ce898c09543 100644 --- a/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs +++ b/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs @@ -30,7 +30,7 @@ public class ResourceDerivedTypeBinderTests [DynamicData(nameof(GetTypesNotInNeedOfBinding), DynamicDataSourceType.Method)] public void Returns_input_if_no_unbound_types_are_enclosed(TypeSymbol type) { - ResourceDerivedTypeBinder sut = new(StrictMock.Of().Object, new SimpleDiagnosticWriter(), TextSpan.TextDocumentStart); + ResourceDerivedTypeBinder sut = new(StrictMock.Of().Object); sut.BindResourceDerivedTypes(type).Should().BeSameAs(type); } @@ -209,7 +209,7 @@ private static (ResourceDerivedTypeBinder sut, ResourceTypeReference unhydratedT var binderMock = StrictMock.Of(); binderMock.Setup(x => x.NamespaceResolver).Returns(resolver); - return (new(binderMock.Object, new SimpleDiagnosticWriter(), TextSpan.TextDocumentStart), unhydratedTypeRef); + return (new(binderMock.Object), unhydratedTypeRef); } [TestMethod] @@ -256,13 +256,10 @@ public void Emits_diagnostic_and_uses_fallback_type_when_resource_not_found() var binderMock = StrictMock.Of(); binderMock.Setup(x => x.NamespaceResolver).Returns(resolver); - var diagnostics = ToListDiagnosticWriter.Create(); - ResourceDerivedTypeBinder sut = new(binderMock.Object, diagnostics, TextSpan.TextDocumentStart); + ResourceDerivedTypeBinder sut = new(binderMock.Object); var fallbackType = LanguageConstants.SecureString; sut.BindResourceDerivedTypes(new UnboundResourceDerivedType(unhydratedTypeRef, fallbackType)).Should().BeSameAs(fallbackType); - diagnostics.GetDiagnostics().Should() - .ContainSingleDiagnostic("BCP081", DiagnosticLevel.Warning, """Resource type "type@version" does not have types available."""); resourceTypeProviderMock.Verify(x => x.TryGetDefinedType(stubbedNamespaceType, unhydratedTypeRef, ResourceTypeGenerationFlags.None), Times.Once()); resourceTypeProviderMock.Verify(x => x.TryGenerateFallbackType(stubbedNamespaceType, unhydratedTypeRef, ResourceTypeGenerationFlags.None), Times.Once()); diff --git a/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs b/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs index 04f4ce808cf..85ba170e656 100644 --- a/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs +++ b/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs @@ -20,7 +20,8 @@ public record ExperimentalFeaturesEnabled( bool ProviderRegistry, bool MicrosoftGraphPreview, bool CompileTimeImports, - bool PublishSource) + bool PublishSource, + bool ResourceDerivedTypes) { public static ExperimentalFeaturesEnabled Bind(JsonElement element) => element.ToNonNullObject(); diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 152993088f9..1911e378de9 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -2106,8 +2106,13 @@ public FixableDiagnostic ProviderDeclarationViaImportKeywordIsDeprecated(Provide public ErrorDiagnostic TypeRequiresParameterization(string typeName, int requiredArgumentCount) => new( TextSpan, - "BCP383", + "BCP384", $"The \"{typeName}\" type requires {requiredArgumentCount} argument(s)."); + + public ErrorDiagnostic ResourceDerivedTypesUnsupported() => new( + TextSpan, + "BCP385", + $@"Using resource-derived types requires enabling EXPERIMENTAL feature ""{nameof(ExperimentalFeaturesEnabled.ResourceDerivedTypes)}""."); } public static DiagnosticBuilderInternal ForPosition(TextSpan span) diff --git a/src/Bicep.Core/Features/FeatureProvider.cs b/src/Bicep.Core/Features/FeatureProvider.cs index 8a9e14ccaa8..c027ffad6f2 100644 --- a/src/Bicep.Core/Features/FeatureProvider.cs +++ b/src/Bicep.Core/Features/FeatureProvider.cs @@ -50,6 +50,8 @@ public FeatureProvider(RootConfiguration configuration) public bool PublishSourceEnabled => configuration.ExperimentalFeaturesEnabled.PublishSource; + public bool ResourceDerivedTypesEnabled => configuration.ExperimentalFeaturesEnabled.ResourceDerivedTypes; + private static bool ReadBooleanEnvVar(string envVar, bool defaultValue) => bool.TryParse(Environment.GetEnvironmentVariable(envVar), out var value) ? value : defaultValue; diff --git a/src/Bicep.Core/Features/IFeatureProvider.cs b/src/Bicep.Core/Features/IFeatureProvider.cs index 44f8c0e9e9d..667d7ff9503 100644 --- a/src/Bicep.Core/Features/IFeatureProvider.cs +++ b/src/Bicep.Core/Features/IFeatureProvider.cs @@ -37,6 +37,8 @@ public interface IFeatureProvider bool PublishSourceEnabled { get; } + bool ResourceDerivedTypesEnabled { get; } + IEnumerable<(string name, bool impactsCompilation, bool usesExperimentalArmEngineFeature)> EnabledFeatureMetadata { get @@ -58,6 +60,7 @@ public interface IFeatureProvider (CompileTimeImportsEnabled, CoreResources.ExperimentalFeatureNames_CompileTimeImports, true, false), (MicrosoftGraphPreviewEnabled, CoreResources.ExperimentalFeatureNames_MicrosoftGraphPreview, true, true), (PublishSourceEnabled, CoreResources.ExperimentalFeatureNames_PublishSource, false, false), + (ResourceDerivedTypesEnabled, "foo", true, false), }) { if (enabled) diff --git a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs index b6b71caa1bb..4472af3ecbb 100644 --- a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs +++ b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs @@ -1728,35 +1728,38 @@ ObjectTypeSyntax @object when TypeHelper.IsLiteralType(typeManager.GetDeclaredTy _ => false, }; - private static IEnumerable GetSystemAmbientSymbols(BicepSourceFileKind sourceFileKind) + private static IEnumerable GetSystemAmbientSymbols(IFeatureProvider features, BicepSourceFileKind sourceFileKind) => sourceFileKind switch { BicepSourceFileKind.ParamsFile => ImmutableArray.Empty, - _ => GetArmPrimitiveTypes().Concat(GetBuiltInUtilityTypes()), + _ => GetArmPrimitiveTypes().Concat(GetBuiltInUtilityTypes(features)), }; private static IEnumerable GetArmPrimitiveTypes() => LanguageConstants.DeclarationTypes.Select(t => new TypeProperty(t.Key, new TypeType(t.Value))); - private static IEnumerable GetBuiltInUtilityTypes() + private static IEnumerable GetBuiltInUtilityTypes(IFeatureProvider features) { - yield return new("resource", new TypeTemplate("resource", - ImmutableArray.Create(new TypeParameter("T")), - (binder, syntax, argumentTypes) => - { - if (argumentTypes.FirstOrDefault() is not StringLiteralType stringLiteral) + if (features.ResourceDerivedTypesEnabled) + { + yield return new("resource", new TypeTemplate("resource", + ImmutableArray.Create(new TypeParameter("T")), + (binder, syntax, argumentTypes) => { - return new(DiagnosticBuilder.ForPosition(TextSpan.BetweenExclusive(syntax.OpenChevron, syntax.CloseChevron)).CompileTimeConstantRequired()); - } + if (argumentTypes.FirstOrDefault() is not StringLiteralType stringLiteral) + { + return new(DiagnosticBuilder.ForPosition(TextSpan.BetweenExclusive(syntax.OpenChevron, syntax.CloseChevron)).CompileTimeConstantRequired()); + } - if (!TypeHelper.GetResourceTypeFromString(binder, stringLiteral.RawStringValue, ResourceTypeGenerationFlags.None, parentResourceType: null) - .IsSuccess(out var resourceType, out var errorBuilder)) - { - return new(errorBuilder(DiagnosticBuilder.ForPosition(syntax.GetArgumentByPosition(0)))); - } + if (!TypeHelper.GetResourceTypeFromString(binder, stringLiteral.RawStringValue, ResourceTypeGenerationFlags.None, parentResourceType: null) + .IsSuccess(out var resourceType, out var errorBuilder)) + { + return new(errorBuilder(DiagnosticBuilder.ForPosition(syntax.GetArgumentByPosition(0)))); + } - return new(new ResourceDerivedTypeExpression(syntax, resourceType, resourceType.Body.Type)); - })); + return new(new ResourceDerivedTypeExpression(syntax, resourceType, resourceType.Body.Type)); + })); + } } public static NamespaceType Create(string aliasName, IFeatureProvider featureProvider, BicepSourceFileKind sourceFileKind) @@ -1764,7 +1767,7 @@ public static NamespaceType Create(string aliasName, IFeatureProvider featurePro return new NamespaceType( aliasName, Settings, - GetSystemAmbientSymbols(sourceFileKind), + GetSystemAmbientSymbols(featureProvider, sourceFileKind), GetSystemOverloads(featureProvider, sourceFileKind), BannedFunctions, GetSystemDecorators(featureProvider, sourceFileKind), diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index ebc034dd48f..5bc1410494d 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -40,7 +40,7 @@ public DeclaredTypeManager(TypeManager typeManager, IBinder binder, IFeatureProv this.typeManager = typeManager; this.binder = binder; this.features = features; - this.resourceDerivedTypeBinder = new(binder, new SimpleDiagnosticWriter(), TextSpan.TextDocumentStart); + this.resourceDerivedTypeBinder = new(binder); } public DeclaredTypeAssignment? GetDeclaredTypeAssignment(SyntaxBase syntax) => diff --git a/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs index 90482b795ad..87ba6bd21c9 100644 --- a/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs +++ b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeBinder.cs @@ -1,25 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Linq; -using System.Numerics; using Bicep.Core.Diagnostics; -using Bicep.Core.Extensions; using Bicep.Core.Parsing; -using Bicep.Core.Resources; using Bicep.Core.Semantics; using Bicep.Core.Semantics.Namespaces; -using Bicep.Core.Text; using Bicep.Core.TypeSystem.Types; -using Bicep.Core.Utils; -using Newtonsoft.Json.Linq; namespace Bicep.Core.TypeSystem; @@ -30,14 +21,10 @@ public class ResourceDerivedTypeBinder private readonly Stack currentlySearchingForUnboundTypes = new(); private readonly ConcurrentDictionary containsUnboundTypesCache = new(); private readonly IBinder binder; - private readonly IDiagnosticWriter diagnostics; - private readonly IPositionable diagnosticTarget; - public ResourceDerivedTypeBinder(IBinder binder, IDiagnosticWriter diagnostics, IPositionable diagnosticTarget) + public ResourceDerivedTypeBinder(IBinder binder) { this.binder = binder; - this.diagnostics = diagnostics; - this.diagnosticTarget = diagnosticTarget; } public TypeSymbol BindResourceDerivedTypes(TypeSymbol unbound) => boundTypes.GetOrAdd(unbound, CalculateTypeBinding); @@ -77,13 +64,7 @@ private TypeSymbol CalculateTypeBinding(IUnboundResourceDerivedType unbound) .Where(resourceType => LanguageConstants.IdentifierComparer.Equals(resourceType.DeclaringNamespace.ProviderName, AzNamespaceType.BuiltInName)) .FirstOrDefault(); - if (bound is null) - { - diagnostics.Write(DiagnosticBuilder.ForPosition(diagnosticTarget).ResourceTypesUnavailable(unbound.TypeReference)); - return unbound.FallbackType; - } - - return bound.Body.Type; + return bound?.Body.Type ?? unbound.FallbackType; } private TupleType CalculateTypeBinding(TupleType unbound) diff --git a/src/Bicep.Core/TypeSystem/ResourceDerivedTypeDiagnosticReporter.cs b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeDiagnosticReporter.cs new file mode 100644 index 00000000000..9b4dbf884c8 --- /dev/null +++ b/src/Bicep.Core/TypeSystem/ResourceDerivedTypeDiagnosticReporter.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using Bicep.Core.Diagnostics; +using Bicep.Core.Extensions; +using Bicep.Core.Features; +using Bicep.Core.Semantics; +using Bicep.Core.Semantics.Namespaces; +using Bicep.Core.TypeSystem.Types; + +namespace Bicep.Core.TypeSystem; + +public class ResourceDerivedTypeDiagnosticReporter +{ + private readonly Stack processing = new(); + private readonly IFeatureProvider features; + private readonly IBinder binder; + + public ResourceDerivedTypeDiagnosticReporter(IFeatureProvider features, IBinder binder) + { + this.features = features; + this.binder = binder; + } + + public IEnumerable ReportResourceDerivedTypeDiagnostics(TypeSymbol typeSymbol) + { + if (processing.Contains(typeSymbol)) + { + yield break; + } + + processing.Push(typeSymbol); + + foreach (var diagnostic in typeSymbol switch + { + IUnboundResourceDerivedType resourceDerivedType => ReportResourceDerivedTypeDiagnostics(resourceDerivedType), + TupleType tuple => tuple.Items.SelectMany(ReportResourceDerivedTypeDiagnostics), + ArrayType array => ReportResourceDerivedTypeDiagnostics(array.Item.Type), + DiscriminatedObjectType taggedUnion => taggedUnion.UnionMembersByKey.Values.SelectMany(ReportResourceDerivedTypeDiagnostics), + ObjectType @object => @object.Properties.Values.Select(property => property.TypeReference).Append(@object.AdditionalPropertiesType) + .SelectMany(ReportResourceDerivedTypeDiagnostics), + UnionType union => union.Members.SelectMany(ReportResourceDerivedTypeDiagnostics), + TypeType typeType => ReportResourceDerivedTypeDiagnostics(typeType.Unwrapped), + LambdaType lambda => lambda.ArgumentTypes.Append(lambda.ReturnType).SelectMany(ReportResourceDerivedTypeDiagnostics), + _ => Enumerable.Empty(), + }) + { + yield return diagnostic; + } + + processing.Pop(); + } + + private IEnumerable ReportResourceDerivedTypeDiagnostics(ITypeReference? typeReference) + => typeReference is not null ? ReportResourceDerivedTypeDiagnostics(typeReference.Type) : Enumerable.Empty(); + + private IEnumerable ReportResourceDerivedTypeDiagnostics(IUnboundResourceDerivedType unbound) + { + if (!features.ResourceDerivedTypesEnabled) + { + yield return x => x.ResourceDerivedTypesUnsupported(); + } + + // TODO support types derived from resources other than the `az` provider. This will require some refactoring of how provider artifacts are restored + var bound = binder.NamespaceResolver.GetMatchingResourceTypes(unbound.TypeReference, ResourceTypeGenerationFlags.None) + .Where(resourceType => LanguageConstants.IdentifierComparer.Equals(resourceType.DeclaringNamespace.ProviderName, AzNamespaceType.BuiltInName)) + .FirstOrDefault(); + + if (bound is null || !bound.DeclaringNamespace.ResourceTypeProvider.HasDefinedType(unbound.TypeReference)) + { + yield return x => x.ResourceTypesUnavailable(unbound.TypeReference); + } + } +} diff --git a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs index 94da84d4e2a..8b9fd6012df 100644 --- a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs +++ b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs @@ -35,6 +35,8 @@ public sealed class TypeAssignmentVisitor : AstVisitor private readonly IDiagnosticLookup parsingErrorLookup; private readonly IArtifactFileLookup sourceFileLookup; private readonly ISemanticModelLookup semanticModelLookup; + private readonly ResourceDerivedTypeBinder resourceDerivedTypeBinder; + private readonly ResourceDerivedTypeDiagnosticReporter resourceDerivedTypeDiagnosticReporter; private readonly ConcurrentDictionary assignedTypes; private readonly ConcurrentDictionary matchedFunctionOverloads; private readonly ConcurrentDictionary matchedFunctionResultValues; @@ -49,6 +51,8 @@ public TypeAssignmentVisitor(ITypeManager typeManager, IFeatureProvider features this.parsingErrorLookup = parsingErrorLookup; this.sourceFileLookup = sourceFileLookup; this.semanticModelLookup = semanticModelLookup; + resourceDerivedTypeBinder = new(binder); + resourceDerivedTypeDiagnosticReporter = new(features, binder); assignedTypes = new(); matchedFunctionOverloads = new(); matchedFunctionResultValues = new(); @@ -82,6 +86,7 @@ public IEnumerable GetAllDiagnostics() Visit(syntax); return matchedFunctionOverloads.TryGetValue(syntax, out var overload) ? overload : null; } + public Expression? GetMatchedFunctionResultValue(FunctionCallSyntaxBase syntax) { Visit(syntax); @@ -422,13 +427,19 @@ public override void VisitModuleDeclarationSyntax(ModuleDeclarationSyntax syntax } } - if (this.binder.GetSymbolInfo(syntax) is ModuleSymbol moduleSymbol && - moduleSymbol.TryGetSemanticModel().IsSuccess(out var moduleSemanticModel, out var _) && - moduleSemanticModel.HasErrors()) + if (this.binder.GetSymbolInfo(syntax) is ModuleSymbol moduleSymbol && moduleSymbol.TryGetSemanticModel().IsSuccess(out var moduleSemanticModel, out var _)) { - diagnostics.Write(moduleSemanticModel is ArmTemplateSemanticModel - ? DiagnosticBuilder.ForPosition(syntax.Path).ReferencedArmTemplateHasErrors() - : DiagnosticBuilder.ForPosition(syntax.Path).ReferencedModuleHasErrors()); + if (moduleSemanticModel.HasErrors()) + { + diagnostics.Write(moduleSemanticModel is ArmTemplateSemanticModel + ? DiagnosticBuilder.ForPosition(syntax.Path).ReferencedArmTemplateHasErrors() + : DiagnosticBuilder.ForPosition(syntax.Path).ReferencedModuleHasErrors()); + } + + diagnostics.WriteMultiple(moduleSemanticModel.Parameters.Values.Select(md => md.TypeReference.Type) + .Concat(moduleSemanticModel.Outputs.Select(md => md.TypeReference.Type)) + .SelectMany(resourceDerivedTypeDiagnosticReporter.ReportResourceDerivedTypeDiagnostics) + .Select(builder => builder(DiagnosticBuilder.ForPosition(syntax.Path)))); } @@ -973,9 +984,11 @@ public override void VisitWildcardImportSyntax(WildcardImportSyntax syntax) List nsProperties = new(); List nsFunctions = new(); - ResourceDerivedTypeBinder resourceDerivedTypeBinder = new(binder, diagnostics, syntax); foreach (var export in importedModel.Exports.Values) { + diagnostics.WriteMultiple(resourceDerivedTypeDiagnosticReporter.ReportResourceDerivedTypeDiagnostics(export.TypeReference.Type) + .Select(builder => builder(DiagnosticBuilder.ForPosition(syntax.Wildcard)))); + if (export is ExportedFunctionMetadata exportedFunction) { nsFunctions.Add(exportedFunction.Overload); @@ -1026,8 +1039,8 @@ public override void VisitImportedSymbolsListItemSyntax(ImportedSymbolsListItemS return ErrorType.Empty(); } - ResourceDerivedTypeBinder resourceDerivedTypeBinder = new(binder, diagnostics, syntax); - + diagnostics.WriteMultiple(resourceDerivedTypeDiagnosticReporter.ReportResourceDerivedTypeDiagnostics(exported.TypeReference.Type) + .Select(builder => builder(DiagnosticBuilder.ForPosition(syntax)))); return resourceDerivedTypeBinder.BindResourceDerivedTypes(exported.TypeReference.Type); }); From 7b109c675658b0d062bcc2560bf1c42585580c96 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 1 Dec 2023 10:17:50 -0500 Subject: [PATCH 06/20] Regenerate baselines --- .../UserDefinedTypeTests.cs | 7 ++++--- .../InvalidExpressions_LF/main.syntax.bicep | 4 ++-- .../InvalidExpressions_LF/main.tokens.bicep | 4 ++-- .../Files/baselines/Lambdas_LF/main.syntax.bicep | 16 ++++++++-------- .../Files/baselines/Lambdas_LF/main.tokens.bicep | 16 ++++++++-------- .../baselines/Variables_LF/main.syntax.bicep | 2 +- .../baselines/Variables_LF/main.tokens.bicep | 2 +- .../Expressions/parameters.syntax.bicepparam | 8 ++++---- .../Expressions/parameters.tokens.bicepparam | 8 ++++---- .../parameters.syntax.bicepparam | 2 +- .../parameters.tokens.bicepparam | 2 +- 11 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs index ef4d2f8ef50..486e54bead4 100644 --- a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs +++ b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs @@ -866,7 +866,8 @@ public void Parsing_incomplete_tuple_type_expressions_halts() [TestMethod] public void Resource_derived_type_should_compile_successfully() { - var result = CompilationHelper.Compile(""" + var result = CompilationHelper.Compile(new UnitTests.ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true)), + """ type myType = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> """); @@ -885,7 +886,7 @@ public void Resource_derived_type_should_compile_successfully() [TestMethod] public void Param_with_resource_derived_type_can_be_loaded() { - var result = CompilationHelper.Compile( + var result = CompilationHelper.Compile(new UnitTests.ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true)), ("main.bicep", """ param location string = resourceGroup().location @@ -938,7 +939,7 @@ public void Param_with_resource_derived_type_can_be_loaded() [TestMethod] public void Output_with_resource_derived_type_can_be_loaded() { - var result = CompilationHelper.Compile( + var result = CompilationHelper.Compile(new UnitTests.ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true)), ("main.bicep", """ module mod 'mod.json' = { name: 'mod' diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.syntax.bicep index 8d6589bb052..2b28d2aa851 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.syntax.bicep @@ -518,7 +518,7 @@ var lt = 4 < 's' //@[09:0016) | └─BinaryOperationSyntax //@[09:0010) | ├─IntegerLiteralSyntax //@[09:0010) | | └─Token(Integer) |4| -//@[11:0012) | ├─Token(LessThan) |<| +//@[11:0012) | ├─Token(LeftChevron) |<| //@[13:0016) | └─StringSyntax //@[13:0016) | └─Token(StringComplete) |'s'| //@[16:0017) ├─Token(NewLine) |\n| @@ -544,7 +544,7 @@ var gt = false>[ //@[09:0018) | └─BinaryOperationSyntax //@[09:0014) | ├─BooleanLiteralSyntax //@[09:0014) | | └─Token(FalseKeyword) |false| -//@[14:0015) | ├─Token(GreaterThan) |>| +//@[14:0015) | ├─Token(RightChevron) |>| //@[15:0018) | └─ArraySyntax //@[15:0016) | ├─Token(LeftSquare) |[| //@[16:0017) | ├─Token(NewLine) |\n| diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.tokens.bicep index a46749b19b9..0b6da07985a 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidExpressions_LF/main.tokens.bicep @@ -343,7 +343,7 @@ var lt = 4 < 's' //@[04:06) Identifier |lt| //@[07:08) Assignment |=| //@[09:10) Integer |4| -//@[11:12) LessThan |<| +//@[11:12) LeftChevron |<| //@[13:16) StringComplete |'s'| //@[16:17) NewLine |\n| var lteq = null <= 10 @@ -359,7 +359,7 @@ var gt = false>[ //@[04:06) Identifier |gt| //@[07:08) Assignment |=| //@[09:14) FalseKeyword |false| -//@[14:15) GreaterThan |>| +//@[14:15) RightChevron |>| //@[15:16) LeftSquare |[| //@[16:17) NewLine |\n| ] diff --git a/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.syntax.bicep index 77a47df21b2..2c87e7e3500 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.syntax.bicep @@ -702,7 +702,7 @@ var sortNumeric = sort([8, 3, 10, -13, 5], (x, y) => x < y) //@[053:0054) | | ├─VariableAccessSyntax //@[053:0054) | | | └─IdentifierSyntax //@[053:0054) | | | └─Token(Identifier) |x| -//@[055:0056) | | ├─Token(LessThan) |<| +//@[055:0056) | | ├─Token(LeftChevron) |<| //@[057:0058) | | └─VariableAccessSyntax //@[057:0058) | | └─IdentifierSyntax //@[057:0058) | | └─Token(Identifier) |y| @@ -751,7 +751,7 @@ var sortAlpha = sort(['ghi', 'abc', 'def'], (x, y) => x < y) //@[054:0055) | | ├─VariableAccessSyntax //@[054:0055) | | | └─IdentifierSyntax //@[054:0055) | | | └─Token(Identifier) |x| -//@[056:0057) | | ├─Token(LessThan) |<| +//@[056:0057) | | ├─Token(LeftChevron) |<| //@[058:0059) | | └─VariableAccessSyntax //@[058:0059) | | └─IdentifierSyntax //@[058:0059) | | └─Token(Identifier) |y| @@ -800,7 +800,7 @@ var sortAlphaReverse = sort(['ghi', 'abc', 'def'], (x, y) => x > y) //@[061:0062) | | ├─VariableAccessSyntax //@[061:0062) | | | └─IdentifierSyntax //@[061:0062) | | | └─Token(Identifier) |x| -//@[063:0064) | | ├─Token(GreaterThan) |>| +//@[063:0064) | | ├─Token(RightChevron) |>| //@[065:0066) | | └─VariableAccessSyntax //@[065:0066) | | └─IdentifierSyntax //@[065:0066) | | └─Token(Identifier) |y| @@ -926,7 +926,7 @@ var sortByObjectKey = sort([ //@[019:0022) | | | | └─IdentifierSyntax //@[019:0022) | | | | └─Token(Identifier) |key| //@[022:0023) | | | └─Token(RightParen) |)| -//@[024:0025) | | ├─Token(LessThan) |<| +//@[024:0025) | | ├─Token(LeftChevron) |<| //@[026:0036) | | └─FunctionCallSyntax //@[026:0029) | | ├─IdentifierSyntax //@[026:0029) | | | └─Token(Identifier) |int| @@ -980,7 +980,7 @@ var sortEmpty = sort([], (x, y) => int(x) < int(y)) //@[039:0040) | | | | └─IdentifierSyntax //@[039:0040) | | | | └─Token(Identifier) |x| //@[040:0041) | | | └─Token(RightParen) |)| -//@[042:0043) | | ├─Token(LessThan) |<| +//@[042:0043) | | ├─Token(LeftChevron) |<| //@[044:0050) | | └─FunctionCallSyntax //@[044:0047) | | ├─IdentifierSyntax //@[044:0047) | | | └─Token(Identifier) |int| @@ -1284,7 +1284,7 @@ var filteredLoop = filter(itemForLoop, i => i > 5) //@[044:0045) | | ├─VariableAccessSyntax //@[044:0045) | | | └─IdentifierSyntax //@[044:0045) | | | └─Token(Identifier) |i| -//@[046:0047) | | ├─Token(GreaterThan) |>| +//@[046:0047) | | ├─Token(RightChevron) |>| //@[048:0049) | | └─IntegerLiteralSyntax //@[048:0049) | | └─Token(Integer) |5| //@[049:0050) | └─Token(RightParen) |)| @@ -1645,7 +1645,7 @@ var objectMap2 = toObject(range(0, 10), i => '${i}', i => { //@[019:0020) | | | | ├─VariableAccessSyntax //@[019:0020) | | | | | └─IdentifierSyntax //@[019:0020) | | | | | └─Token(Identifier) |i| -//@[021:0022) | | | | ├─Token(GreaterThan) |>| +//@[021:0022) | | | | ├─Token(RightChevron) |>| //@[023:0024) | | | | └─IntegerLiteralSyntax //@[023:0024) | | | | └─Token(Integer) |4| //@[024:0025) | | | └─Token(RightParen) |)| @@ -1827,7 +1827,7 @@ var objectMap6 = toObject(range(0, 10), i => '${i}', i => // comment //@[019:0020) | | | | ├─VariableAccessSyntax //@[019:0020) | | | | | └─IdentifierSyntax //@[019:0020) | | | | | └─Token(Identifier) |i| -//@[021:0022) | | | | ├─Token(GreaterThan) |>| +//@[021:0022) | | | | ├─Token(RightChevron) |>| //@[023:0024) | | | | └─IntegerLiteralSyntax //@[023:0024) | | | | └─Token(Integer) |4| //@[024:0025) | | | └─Token(RightParen) |)| diff --git a/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.tokens.bicep index b733941e6b0..7159a235bb4 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Lambdas_LF/main.tokens.bicep @@ -373,7 +373,7 @@ var sortNumeric = sort([8, 3, 10, -13, 5], (x, y) => x < y) //@[048:049) RightParen |)| //@[050:052) Arrow |=>| //@[053:054) Identifier |x| -//@[055:056) LessThan |<| +//@[055:056) LeftChevron |<| //@[057:058) Identifier |y| //@[058:059) RightParen |)| //@[059:060) NewLine |\n| @@ -398,7 +398,7 @@ var sortAlpha = sort(['ghi', 'abc', 'def'], (x, y) => x < y) //@[049:050) RightParen |)| //@[051:053) Arrow |=>| //@[054:055) Identifier |x| -//@[056:057) LessThan |<| +//@[056:057) LeftChevron |<| //@[058:059) Identifier |y| //@[059:060) RightParen |)| //@[060:061) NewLine |\n| @@ -423,7 +423,7 @@ var sortAlphaReverse = sort(['ghi', 'abc', 'def'], (x, y) => x > y) //@[056:057) RightParen |)| //@[058:060) Arrow |=>| //@[061:062) Identifier |x| -//@[063:064) GreaterThan |>| +//@[063:064) RightChevron |>| //@[065:066) Identifier |y| //@[066:067) RightParen |)| //@[067:068) NewLine |\n| @@ -494,7 +494,7 @@ var sortByObjectKey = sort([ //@[018:019) Dot |.| //@[019:022) Identifier |key| //@[022:023) RightParen |)| -//@[024:025) LessThan |<| +//@[024:025) LeftChevron |<| //@[026:029) Identifier |int| //@[029:030) LeftParen |(| //@[030:031) Identifier |y| @@ -522,7 +522,7 @@ var sortEmpty = sort([], (x, y) => int(x) < int(y)) //@[038:039) LeftParen |(| //@[039:040) Identifier |x| //@[040:041) RightParen |)| -//@[042:043) LessThan |<| +//@[042:043) LeftChevron |<| //@[044:047) Identifier |int| //@[047:048) LeftParen |(| //@[048:049) Identifier |y| @@ -685,7 +685,7 @@ var filteredLoop = filter(itemForLoop, i => i > 5) //@[039:040) Identifier |i| //@[041:043) Arrow |=>| //@[044:045) Identifier |i| -//@[046:047) GreaterThan |>| +//@[046:047) RightChevron |>| //@[048:049) Integer |5| //@[049:050) RightParen |)| //@[050:052) NewLine |\n\n| @@ -885,7 +885,7 @@ var objectMap2 = toObject(range(0, 10), i => '${i}', i => { //@[016:017) Colon |:| //@[018:019) LeftParen |(| //@[019:020) Identifier |i| -//@[021:022) GreaterThan |>| +//@[021:022) RightChevron |>| //@[023:024) Integer |4| //@[024:025) RightParen |)| //@[025:026) NewLine |\n| @@ -983,7 +983,7 @@ var objectMap6 = toObject(range(0, 10), i => '${i}', i => // comment //@[016:017) Colon |:| //@[018:019) LeftParen |(| //@[019:020) Identifier |i| -//@[021:022) GreaterThan |>| +//@[021:022) RightChevron |>| //@[023:024) Integer |4| //@[024:025) RightParen |)| //@[025:026) NewLine |\n| diff --git a/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.syntax.bicep index 2e9488e443f..f1fb9213a4a 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.syntax.bicep @@ -426,7 +426,7 @@ var myObj = { //@[012:0020) | | | | | ├─BinaryOperationSyntax //@[012:0015) | | | | | | ├─IntegerLiteralSyntax //@[012:0015) | | | | | | | └─Token(Integer) |144| -//@[016:0017) | | | | | | ├─Token(GreaterThan) |>| +//@[016:0017) | | | | | | ├─Token(RightChevron) |>| //@[018:0020) | | | | | | └─IntegerLiteralSyntax //@[018:0020) | | | | | | └─Token(Integer) |33| //@[021:0023) | | | | | ├─Token(LogicalAnd) |&&| diff --git a/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.tokens.bicep index 51ab6f343f7..ce4b0a49d46 100644 --- a/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/Variables_LF/main.tokens.bicep @@ -274,7 +274,7 @@ var myObj = { //@[006:010) Identifier |test| //@[010:011) Colon |:| //@[012:015) Integer |144| -//@[016:017) GreaterThan |>| +//@[016:017) RightChevron |>| //@[018:020) Integer |33| //@[021:023) LogicalAnd |&&| //@[024:028) TrueKeyword |true| diff --git a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.syntax.bicepparam b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.syntax.bicepparam index 04b4a951410..87edd5c57bf 100644 --- a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.syntax.bicepparam +++ b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.syntax.bicepparam @@ -274,7 +274,7 @@ param myObject = { //@[30:0031) | | | ├─VariableAccessSyntax //@[30:0031) | | | | └─IdentifierSyntax //@[30:0031) | | | | └─Token(Identifier) |i| -//@[32:0033) | | | ├─Token(LessThan) |<| +//@[32:0033) | | | ├─Token(LeftChevron) |<| //@[34:0035) | | | └─IntegerLiteralSyntax //@[34:0035) | | | └─Token(Integer) |2| //@[35:0036) | | └─Token(RightParen) |)| @@ -868,7 +868,7 @@ param myObject = { //@[40:0041) | | | ├─VariableAccessSyntax //@[40:0041) | | | | └─IdentifierSyntax //@[40:0041) | | | | └─Token(Identifier) |a| -//@[42:0043) | | | ├─Token(LessThan) |<| +//@[42:0043) | | | ├─Token(LeftChevron) |<| //@[44:0045) | | | └─VariableAccessSyntax //@[44:0045) | | | └─IdentifierSyntax //@[44:0045) | | | └─Token(Identifier) |b| @@ -1276,7 +1276,7 @@ param myArray = [ //@[02:0007) | | └─BinaryOperationSyntax //@[02:0003) | | ├─IntegerLiteralSyntax //@[02:0003) | | | └─Token(Integer) |1| -//@[04:0005) | | ├─Token(LessThan) |<| +//@[04:0005) | | ├─Token(LeftChevron) |<| //@[06:0007) | | └─IntegerLiteralSyntax //@[06:0007) | | └─Token(Integer) |2| //@[07:0008) | ├─Token(NewLine) |\n| @@ -1285,7 +1285,7 @@ param myArray = [ //@[02:0007) | | └─BinaryOperationSyntax //@[02:0003) | | ├─IntegerLiteralSyntax //@[02:0003) | | | └─Token(Integer) |1| -//@[04:0005) | | ├─Token(GreaterThan) |>| +//@[04:0005) | | ├─Token(RightChevron) |>| //@[06:0007) | | └─IntegerLiteralSyntax //@[06:0007) | | └─Token(Integer) |2| //@[07:0008) | ├─Token(NewLine) |\n| diff --git a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.tokens.bicepparam b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.tokens.bicepparam index b7d653a735d..4a100b1f9a4 100644 --- a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.tokens.bicepparam +++ b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Expressions/parameters.tokens.bicepparam @@ -150,7 +150,7 @@ param myObject = { //@[25:26) Identifier |i| //@[27:29) Arrow |=>| //@[30:31) Identifier |i| -//@[32:33) LessThan |<| +//@[32:33) LeftChevron |<| //@[34:35) Integer |2| //@[35:36) RightParen |)| //@[36:37) NewLine |\n| @@ -471,7 +471,7 @@ param myObject = { //@[35:36) RightParen |)| //@[37:39) Arrow |=>| //@[40:41) Identifier |a| -//@[42:43) LessThan |<| +//@[42:43) LeftChevron |<| //@[44:45) Identifier |b| //@[45:46) RightParen |)| //@[46:47) NewLine |\n| @@ -700,12 +700,12 @@ param myArray = [ //@[08:09) NewLine |\n| 1 < 2 //@[02:03) Integer |1| -//@[04:05) LessThan |<| +//@[04:05) LeftChevron |<| //@[06:07) Integer |2| //@[07:08) NewLine |\n| 1 > 2 //@[02:03) Integer |1| -//@[04:05) GreaterThan |>| +//@[04:05) RightChevron |>| //@[06:07) Integer |2| //@[07:08) NewLine |\n| 1 >= 2 diff --git a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.syntax.bicepparam b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.syntax.bicepparam index 298cd4ee400..8b2ab09cb02 100644 --- a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.syntax.bicepparam +++ b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.syntax.bicepparam @@ -303,7 +303,7 @@ param testFilter = filter([1, 2], i => i < 'foo') //@[39:0040) | | ├─VariableAccessSyntax //@[39:0040) | | | └─IdentifierSyntax //@[39:0040) | | | └─Token(Identifier) |i| -//@[41:0042) | | ├─Token(LessThan) |<| +//@[41:0042) | | ├─Token(LeftChevron) |<| //@[43:0048) | | └─StringSyntax //@[43:0048) | | └─Token(StringComplete) |'foo'| //@[48:0049) | └─Token(RightParen) |)| diff --git a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.tokens.bicepparam b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.tokens.bicepparam index 482e616d108..fc8be615f18 100644 --- a/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.tokens.bicepparam +++ b/src/Bicep.Core.Samples/Files/baselines_bicepparam/Invalid_Expressions/parameters.tokens.bicepparam @@ -171,7 +171,7 @@ param testFilter = filter([1, 2], i => i < 'foo') //@[34:35) Identifier |i| //@[36:38) Arrow |=>| //@[39:40) Identifier |i| -//@[41:42) LessThan |<| +//@[41:42) LeftChevron |<| //@[43:48) StringComplete |'foo'| //@[48:49) RightParen |)| //@[49:50) NewLine |\n| From ab981b8577a6219753a201f47f465c9e18a873db Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 1 Dec 2023 10:37:41 -0500 Subject: [PATCH 07/20] Ensure unbound types are replaced in imported function overloads --- .../Semantics/ImportedFunctionSymbol.cs | 10 ++++-- .../Semantics/Metadata/ExportMetadata.cs | 22 +------------ .../WildcardImportInstanceFunctionSymbol.cs | 2 +- .../TypeSystem/TypeAssignmentVisitor.cs | 2 +- src/Bicep.Core/TypeSystem/TypeHelper.cs | 33 +++++++++++++++---- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/Bicep.Core/Semantics/ImportedFunctionSymbol.cs b/src/Bicep.Core/Semantics/ImportedFunctionSymbol.cs index 6aaa996e576..c001b1385c1 100644 --- a/src/Bicep.Core/Semantics/ImportedFunctionSymbol.cs +++ b/src/Bicep.Core/Semantics/ImportedFunctionSymbol.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections.Immutable; using Bicep.Core.Semantics.Metadata; using Bicep.Core.Syntax; @@ -9,12 +10,17 @@ namespace Bicep.Core.Semantics; public class ImportedFunctionSymbol : ImportedSymbol, IFunctionSymbol { + private readonly Lazy overloadLazy; + public ImportedFunctionSymbol(ISymbolContext context, ImportedSymbolsListItemSyntax declaringSyntax, CompileTimeImportDeclarationSyntax enclosingDeclartion, ISemanticModel sourceModel, ExportedFunctionMetadata exportedFunctionMetadata) - : base(context, declaringSyntax, enclosingDeclartion, sourceModel, exportedFunctionMetadata) { } + : base(context, declaringSyntax, enclosingDeclartion, sourceModel, exportedFunctionMetadata) + { + overloadLazy = new(() => TypeHelper.OverloadWithBoundTypes(new(context.Binder), exportedFunctionMetadata)); + } public override SymbolKind Kind => SymbolKind.Function; - public FunctionOverload Overload => ExportMetadata.Overload; + public FunctionOverload Overload => overloadLazy.Value; public ImmutableArray Overloads => ImmutableArray.Create(Overload); diff --git a/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs b/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs index c1fe07d1f1b..d66c2746e0f 100644 --- a/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs +++ b/src/Bicep.Core/Semantics/Metadata/ExportMetadata.cs @@ -29,27 +29,7 @@ public record ExportedFunctionParameterMetadata(string Name, ITypeReference Type public record ExportedFunctionReturnMetadata(ITypeReference TypeReference, string? Description); public record ExportedFunctionMetadata(string Name, ImmutableArray Parameters, ExportedFunctionReturnMetadata Return, string? Description) - : ExportMetadata(ExportMetadataKind.Function, Name, new LambdaType(Parameters.Select(md => md.TypeReference).ToImmutableArray(), Return.TypeReference), Description) -{ - private readonly Lazy functionOverloadLazy = new(() => - { - var builder = new FunctionOverloadBuilder(Name).WithReturnType(Return.TypeReference.Type); - - if (Description is string description) - { - builder = builder.WithGenericDescription(description).WithDescription(description); - } - - foreach (var param in Parameters) - { - builder = builder.WithRequiredParameter(param.Name, param.TypeReference.Type, param.Description ?? string.Empty); - } - - return builder.Build(); - }); - - public FunctionOverload Overload => functionOverloadLazy.Value; -} + : ExportMetadata(ExportMetadataKind.Function, Name, new LambdaType(Parameters.Select(md => md.TypeReference).ToImmutableArray(), Return.TypeReference), Description); public record DuplicatedExportMetadata(string Name, ImmutableArray ExportKindsWithSameName) : ExportMetadata(ExportMetadataKind.Error, Name, ErrorType.Empty(), $"The name \"{Name}\" is ambiguous because it refers to exports of the following kinds: {string.Join(", ", ExportKindsWithSameName)}."); diff --git a/src/Bicep.Core/Semantics/WildcardImportInstanceFunctionSymbol.cs b/src/Bicep.Core/Semantics/WildcardImportInstanceFunctionSymbol.cs index be97d54c499..2ba39bd5e76 100644 --- a/src/Bicep.Core/Semantics/WildcardImportInstanceFunctionSymbol.cs +++ b/src/Bicep.Core/Semantics/WildcardImportInstanceFunctionSymbol.cs @@ -15,7 +15,7 @@ public WildcardImportInstanceFunctionSymbol(WildcardImportSymbol baseSymbol, str : base(name) { BaseSymbol = baseSymbol; - Overloads = ImmutableArray.Create(exportMetadata.Overload); + Overloads = ImmutableArray.Create(TypeHelper.OverloadWithBoundTypes(new(baseSymbol.Context.Binder), exportMetadata)); } public override void Accept(SymbolVisitor visitor) => visitor.VisitWildcardImportInstanceFunctionSymbol(this); diff --git a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs index 8b9fd6012df..a6088b2eb16 100644 --- a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs +++ b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs @@ -991,7 +991,7 @@ public override void VisitWildcardImportSyntax(WildcardImportSyntax syntax) if (export is ExportedFunctionMetadata exportedFunction) { - nsFunctions.Add(exportedFunction.Overload); + nsFunctions.Add(TypeHelper.OverloadWithBoundTypes(resourceDerivedTypeBinder, exportedFunction)); } else { diff --git a/src/Bicep.Core/TypeSystem/TypeHelper.cs b/src/Bicep.Core/TypeSystem/TypeHelper.cs index 1e89b79df71..ddbb93e61eb 100644 --- a/src/Bicep.Core/TypeSystem/TypeHelper.cs +++ b/src/Bicep.Core/TypeSystem/TypeHelper.cs @@ -13,6 +13,7 @@ using Bicep.Core.Parsing; using Bicep.Core.Resources; using Bicep.Core.Semantics; +using Bicep.Core.Semantics.Metadata; using Bicep.Core.Text; using Bicep.Core.TypeSystem.Types; using Newtonsoft.Json.Linq; @@ -444,6 +445,31 @@ public static ResultWithDiagnostic GetResourceTypeFromString(IBind }; } + public static bool SatisfiesCondition(TypeSymbol typeSymbol, Func conditionFunc) + => typeSymbol switch + { + UnionType unionType => unionType.Members.All(t => conditionFunc(t.Type)), + _ => conditionFunc(typeSymbol), + }; + + public static FunctionOverload OverloadWithBoundTypes(ResourceDerivedTypeBinder binder, ExportedFunctionMetadata exportedFunction) + { + FunctionOverloadBuilder builder = new(exportedFunction.Name); + if (exportedFunction.Description is string description) + { + builder = builder.WithGenericDescription(description).WithDescription(description); + } + + foreach (var param in exportedFunction.Parameters) + { + builder = builder.WithRequiredParameter(param.Name, + binder.BindResourceDerivedTypes(param.TypeReference.Type), + param.Description ?? string.Empty); + } + + return builder.WithReturnType(binder.BindResourceDerivedTypes(exportedFunction.Return.TypeReference.Type)).Build(); + } + private static ImmutableArray NormalizeTypeList(IEnumerable unionMembers) { HashSet distinctMembers = new(); @@ -481,13 +507,6 @@ private static IEnumerable FlattenMembers(IEnumerable unionMembers) => unionMembers.Select(m => m.Type.FormatNameForCompoundTypes()).ConcatString(" | "); - public static bool SatisfiesCondition(TypeSymbol typeSymbol, Func conditionFunc) - => typeSymbol switch - { - UnionType unionType => unionType.Members.All(t => conditionFunc(t.Type)), - _ => conditionFunc(typeSymbol), - }; - private static ResultWithDiagnostic GetCombinedTypeReference(ResourceTypeGenerationFlags flags, ResourceType? parentResourceType, string typeString) { if (ResourceTypeReference.TryParse(typeString) is not { } typeReference) From be98da0b78e11fb15813bcb2b185ff85961b748b Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 1 Dec 2023 11:18:05 -0500 Subject: [PATCH 08/20] Update config schema and tests to reflect new feature flag --- .../Configuration/ConfigurationManagerTests.cs | 12 ++++++++---- src/vscode-bicep/schemas/bicepconfig.schema.json | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs b/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs index a07bc3c98bf..e53bc0c1d1e 100644 --- a/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs +++ b/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs @@ -116,7 +116,8 @@ public void GetBuiltInConfiguration_NoParameter_ReturnsBuiltInConfigurationWithA "providerRegistry": false, "microsoftGraphPreview": false, "compileTimeImports": false, - "publishSource": false + "publishSource": false, + "resourceDerivedTypes": false }, "formatting": { "indentKind": "Space", @@ -198,7 +199,8 @@ public void GetBuiltInConfiguration_DisableAllAnalyzers_ReturnsBuiltInConfigurat "providerRegistry": false, "microsoftGraphPreview": false, "compileTimeImports": false, - "publishSource": false + "publishSource": false, + "resourceDerivedTypes": false }, "formatting": { "indentKind": "Space", @@ -305,7 +307,8 @@ public void GetBuiltInConfiguration_DisableAnalyzers_ReturnsBuiltInConfiguration "providerRegistry": false, "microsoftGraphPreview": false, "compileTimeImports": false, - "publishSource": false + "publishSource": false, + "resourceDerivedTypes": false }, "formatting": { "indentKind": "Space", @@ -740,7 +743,8 @@ public void GetConfiguration_ValidCustomConfiguration_OverridesBuiltInConfigurat "providerRegistry": false, "microsoftGraphPreview": false, "compileTimeImports": false, - "publishSource": false + "publishSource": false, + "resourceDerivedTypes": false }, "formatting": { "indentKind": "Space", diff --git a/src/vscode-bicep/schemas/bicepconfig.schema.json b/src/vscode-bicep/schemas/bicepconfig.schema.json index 918288a11d5..5c7a403a48f 100644 --- a/src/vscode-bicep/schemas/bicepconfig.schema.json +++ b/src/vscode-bicep/schemas/bicepconfig.schema.json @@ -716,6 +716,9 @@ }, "publishSource": { "type": "boolean" + }, + "resourceDerivedTypes": { + "type": "boolean" } }, "additionalProperties": false From 84f72bfb83c6b7cb11e1e0ec77ae5c8bb6cf2d27 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Wed, 6 Dec 2023 16:33:23 -0500 Subject: [PATCH 09/20] Support namespace-qualified parameterized types --- .../UserDefinedTypeTests.cs | 21 ++++++++ .../Parsing/ParserTests.cs | 18 ++++++- .../Intermediate/ExpressionBuilder.cs | 2 +- src/Bicep.Core/Parsing/BaseParser.cs | 35 ++++++++---- .../SyntaxLayouts.SyntaxVisitor.cs | 3 ++ src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs | 13 +++++ src/Bicep.Core/Semantics/ITypeManager.cs | 2 +- src/Bicep.Core/Syntax/AstVisitor.cs | 7 +++ src/Bicep.Core/Syntax/CstVisitor.cs | 10 ++++ src/Bicep.Core/Syntax/ISyntaxVisitor.cs | 2 + ...nceParameterizedTypeInstantiationSyntax.cs | 28 ++++++++++ .../ParameterizedTypeInstantiationSyntax.cs | 27 +--------- ...arameterizedTypeInstantiationSyntaxBase.cs | 38 +++++++++++++ src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs | 27 ++++++++-- src/Bicep.Core/Syntax/SyntaxVisitor.cs | 2 + .../TypeSystem/DeclaredTypeManager.cs | 54 +++++++++++++++---- .../TypeSystem/TypeAssignmentVisitor.cs | 3 -- src/Bicep.Core/TypeSystem/TypeManager.cs | 2 +- .../TypeSystem/Types/TypeTemplate.cs | 2 +- 19 files changed, 239 insertions(+), 57 deletions(-) create mode 100644 src/Bicep.Core/Syntax/InstanceParameterizedTypeInstantiationSyntax.cs create mode 100644 src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntaxBase.cs diff --git a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs index 486e54bead4..f3a1e036848 100644 --- a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs +++ b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs @@ -883,6 +883,27 @@ public void Resource_derived_type_should_compile_successfully() """)); } + [TestMethod] + public void Resource_derived_type_should_compile_successfully_with_namespace_qualified_syntax() + { + var result = CompilationHelper.Compile(new UnitTests.ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true)), + """ + var resource = 'foo' + type myType = sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + """); + + result.Template.Should().HaveValueAtPath("definitions", JToken.Parse($$""" + { + "myType": { + "type": "object", + "metadata": { + "{{LanguageConstants.MetadataResourceDerivedTypePropertyName}}": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + """)); + } + [TestMethod] public void Param_with_resource_derived_type_can_be_loaded() { diff --git a/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs b/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs index 1a667a254df..a8bcdb313f8 100644 --- a/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs +++ b/src/Bicep.Core.UnitTests/Parsing/ParserTests.cs @@ -537,7 +537,7 @@ public void Wildcard_import_should_parse_successfully() } [TestMethod] - public void Utility_type_should_parse_successfully() + public void Parameterized_type_should_parse_successfully() { var typeStatement = "type saType = resource<'Microsoft.Storage/storageAccounts@2022-09-01'>"; @@ -552,6 +552,22 @@ public void Utility_type_should_parse_successfully() singleParam.TryGetLiteralValue().Should().Be("Microsoft.Storage/storageAccounts@2022-09-01"); } + [TestMethod] + public void Qualified_parameterized_type_should_parse_successfully() + { + var typeStatement = "type saType = sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'>"; + + var parsed = ParserHelper.Parse(typeStatement); + var statement = parsed.Declarations.Single().Should().BeOfType().Subject; + + var imported = statement.Value.Should().BeOfType().Subject; + imported.PropertyName.IdentifierName.Should().Be("resource"); + imported.Arguments.Should().HaveCount(1); + + var singleParam = imported.Arguments.Single().Expression.Should().BeOfType().Subject; + singleParam.TryGetLiteralValue().Should().Be("Microsoft.Storage/storageAccounts@2022-09-01"); + } + private static SyntaxBase RunExpressionTest(string text, string expected, Type expectedRootType) { SyntaxBase expression = ParserHelper.ParseExpression(text); diff --git a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs index 7dd78be5587..3c45f058c8e 100644 --- a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs +++ b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs @@ -296,7 +296,7 @@ UnionTypeSyntax unionTypeSyntax when Context.SemanticModel.GetTypeInfo(unionType NonNullAssertionSyntax nonNullAssertion => new NonNullableTypeExpression(nonNullAssertion, ConvertTypeWithoutLowering(nonNullAssertion.BaseExpression)), PropertyAccessSyntax propertyAccess => ConvertPropertyAccessInTypeExpression(propertyAccess), ArrayAccessSyntax arrayAccess => ConvertPropertyAccessInTypeExpression(arrayAccess), - ParameterizedTypeInstantiationSyntax parameterizedTypeInstantiation + ParameterizedTypeInstantiationSyntaxBase parameterizedTypeInstantiation => Context.SemanticModel.TypeManager.TryGetReifiedType(parameterizedTypeInstantiation) is TypeExpression reified ? reified : throw new ArgumentException($"Failed to reify parameterized type invocation."), diff --git a/src/Bicep.Core/Parsing/BaseParser.cs b/src/Bicep.Core/Parsing/BaseParser.cs index d10909c1813..a99a36ed275 100644 --- a/src/Bicep.Core/Parsing/BaseParser.cs +++ b/src/Bicep.Core/Parsing/BaseParser.cs @@ -430,7 +430,7 @@ private SyntaxBase ParameterizedTypeArgument() return new ParameterizedTypeArgumentSyntax(expression); } - protected (IdentifierSyntax Identifier, Token OpenChevron, IEnumerable ParameterNodes, Token CloseChevron) ParameterizedTypeInstantiation(IdentifierSyntax utilityTypeName) + protected (IdentifierSyntax Identifier, Token OpenChevron, IEnumerable ParameterNodes, Token CloseChevron) ParameterizedTypeInstantiation(IdentifierSyntax parameterizedTypeName) { var openChevron = this.Expect(TokenType.LeftChevron, b => b.ExpectedCharacter("<")); @@ -440,23 +440,23 @@ private SyntaxBase ParameterizedTypeArgument() var closeChevron = this.Expect(TokenType.RightChevron, b => b.ExpectedCharacter(">")); - return (utilityTypeName, openChevron, itemsOrTokens, closeChevron); + return (parameterizedTypeName, openChevron, itemsOrTokens, closeChevron); } - private SyntaxBase UtilityTypeOrTypeVariableAccess() + private SyntaxBase ParameterizedTypeOrTypeVariableAccess() { var identifierToken = Expect(TokenType.Identifier, b => b.ExpectedTypeIdentifier()); var identifier = new IdentifierSyntax(identifierToken); if (Check(TokenType.LeftChevron)) { - var utilityType = ParameterizedTypeInstantiation(identifier); + var parameterizedType = ParameterizedTypeInstantiation(identifier); return new ParameterizedTypeInstantiationSyntax( - utilityType.Identifier, - utilityType.OpenChevron, - utilityType.ParameterNodes, - utilityType.CloseChevron); + parameterizedType.Identifier, + parameterizedType.OpenChevron, + parameterizedType.ParameterNodes, + parameterizedType.CloseChevron); } return new VariableAccessSyntax(identifier); @@ -963,7 +963,22 @@ private SyntaxBase MemberTypeExpression() IdentifierSyntax identifier = this.IdentifierOrSkip(b => b.ExpectedFunctionOrPropertyName()); - current = new PropertyAccessSyntax(current, dot, null, identifier); + if (Check(TokenType.LeftChevron)) + { + var parameterizedType = ParameterizedTypeInstantiation(identifier); + + current = new InstanceParameterizedTypeInstantiationSyntax( + current, + dot, + parameterizedType.Identifier, + parameterizedType.OpenChevron, + parameterizedType.ParameterNodes, + parameterizedType.CloseChevron); + } + else + { + current = new PropertyAccessSyntax(current, dot, null, identifier); + } continue; } @@ -1249,7 +1264,7 @@ private SyntaxBase PrimaryTypeExpression() return this.ParenthesizedTypeExpression(); case TokenType.Identifier: - return this.UtilityTypeOrTypeVariableAccess(); + return this.ParameterizedTypeOrTypeVariableAccess(); default: throw new ExpectedTokenException(nextToken, b => b.UnrecognizedTypeExpression()); diff --git a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs index 59fcccf8083..09829633445 100644 --- a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs +++ b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.SyntaxVisitor.cs @@ -169,6 +169,9 @@ public SyntaxLayouts(PrettyPrinterV2Context context) public void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) => this.Apply(syntax, LayoutParameterizedTypeInstantiationSyntax); + public void VisitInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax) + => this.Apply(syntax, LayoutInstanceParameterizedTypeInstantiationSyntax); + public void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) => this.Layout(syntax.Expression); public IEnumerable Layout(SyntaxBase syntax) diff --git a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs index cf167688e19..7f52d3b8d26 100644 --- a/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs +++ b/src/Bicep.Core/PrettyPrintV2/SyntaxLayouts.cs @@ -561,6 +561,19 @@ public IEnumerable LayoutParameterizedTypeInstantiationSyntax(Paramete padding: LineOrEmpty, forceBreak: StartsWithNewline(syntax.Children) && syntax.Arguments.Any())); + private IEnumerable LayoutInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax) => + this.Glue( + syntax.BaseExpression, + syntax.Dot, + syntax.PropertyName, + this.Bracket( + syntax.OpenChevron, + syntax.Children, + syntax.CloseChevron, + separator: CommaLineOrCommaSpace, + padding: LineOrEmpty, + forceBreak: StartsWithNewline(syntax.Children) && syntax.Arguments.Any())); + private IEnumerable LayoutLeadingNodes(IEnumerable leadingNodes) => this.LayoutMany(leadingNodes) .Where(x => x != HardLine); // Remove empty lines between decorators. diff --git a/src/Bicep.Core/Semantics/ITypeManager.cs b/src/Bicep.Core/Semantics/ITypeManager.cs index f96a316c438..440c6a180da 100644 --- a/src/Bicep.Core/Semantics/ITypeManager.cs +++ b/src/Bicep.Core/Semantics/ITypeManager.cs @@ -22,6 +22,6 @@ public interface ITypeManager Expression? GetMatchedFunctionResultValue(FunctionCallSyntaxBase syntax); - TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntax syntax); + TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntaxBase syntax); } } diff --git a/src/Bicep.Core/Syntax/AstVisitor.cs b/src/Bicep.Core/Syntax/AstVisitor.cs index a32075f96d8..f13d0fe0de6 100644 --- a/src/Bicep.Core/Syntax/AstVisitor.cs +++ b/src/Bicep.Core/Syntax/AstVisitor.cs @@ -422,6 +422,13 @@ public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedType this.VisitNodes(syntax.Children); } + public override void VisitInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax) + { + this.Visit(syntax.BaseExpression); + this.Visit(syntax.PropertyName); + this.VisitNodes(syntax.Children); + } + public override void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) { this.Visit(syntax.Expression); diff --git a/src/Bicep.Core/Syntax/CstVisitor.cs b/src/Bicep.Core/Syntax/CstVisitor.cs index 50f48d6311f..215460dbee8 100644 --- a/src/Bicep.Core/Syntax/CstVisitor.cs +++ b/src/Bicep.Core/Syntax/CstVisitor.cs @@ -526,6 +526,16 @@ public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedType this.Visit(syntax.CloseChevron); } + public override void VisitInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax) + { + this.Visit(syntax.BaseExpression); + this.Visit(syntax.Dot); + this.Visit(syntax.PropertyName); + this.Visit(syntax.OpenChevron); + this.VisitNodes(syntax.Children); + this.Visit(syntax.CloseChevron); + } + public override void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) { this.Visit(syntax.Expression); diff --git a/src/Bicep.Core/Syntax/ISyntaxVisitor.cs b/src/Bicep.Core/Syntax/ISyntaxVisitor.cs index 4dcb36be627..ae990b342c4 100644 --- a/src/Bicep.Core/Syntax/ISyntaxVisitor.cs +++ b/src/Bicep.Core/Syntax/ISyntaxVisitor.cs @@ -144,6 +144,8 @@ public interface ISyntaxVisitor void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax); + void VisitInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax); + void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax); } } diff --git a/src/Bicep.Core/Syntax/InstanceParameterizedTypeInstantiationSyntax.cs b/src/Bicep.Core/Syntax/InstanceParameterizedTypeInstantiationSyntax.cs new file mode 100644 index 00000000000..c107813f1c5 --- /dev/null +++ b/src/Bicep.Core/Syntax/InstanceParameterizedTypeInstantiationSyntax.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public class InstanceParameterizedTypeInstantiationSyntax : ParameterizedTypeInstantiationSyntaxBase +{ + public InstanceParameterizedTypeInstantiationSyntax(SyntaxBase baseExpression, Token dot, IdentifierSyntax name, Token openChevron, IEnumerable children, Token closeChevron) + : base(name, openChevron, children, closeChevron) + { + AssertTokenType(dot, nameof(dot), TokenType.Dot); + + this.BaseExpression = baseExpression; + this.Dot = dot; + } + + public SyntaxBase BaseExpression { get; } + + public Token Dot { get; } + + public IdentifierSyntax PropertyName => Name; + + public override TextSpan Span => TextSpan.Between(BaseExpression, CloseChevron); + + public override void Accept(ISyntaxVisitor visitor) => visitor.VisitInstanceParameterizedTypeInstantiationSyntax(this); +} diff --git a/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs b/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs index 9712d2298d2..4f7d94d9950 100644 --- a/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs +++ b/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntax.cs @@ -1,37 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using Bicep.Core.Parsing; namespace Bicep.Core.Syntax; -public class ParameterizedTypeInstantiationSyntax : TypeSyntax +public class ParameterizedTypeInstantiationSyntax : ParameterizedTypeInstantiationSyntaxBase { public ParameterizedTypeInstantiationSyntax(IdentifierSyntax name, Token openChevron, IEnumerable children, Token closeChevron) - { - AssertTokenType(openChevron, nameof(openChevron), TokenType.LeftChevron); - AssertTokenType(closeChevron, nameof(closeChevron), TokenType.RightChevron); - - this.Name = name; - this.OpenChevron = openChevron; - this.Children = children.ToImmutableArray(); - this.CloseChevron = closeChevron; - this.Arguments = children.OfType().ToImmutableArray(); - } - - public IdentifierSyntax Name { get; } - - public Token OpenChevron { get; } - - public ImmutableArray Children { get; } - - public ImmutableArray Arguments { get; } - - public Token CloseChevron { get; } - - public ParameterizedTypeArgumentSyntax GetArgumentByPosition(int index) => Arguments[index]; + : base(name, openChevron, children, closeChevron) { } public override TextSpan Span => TextSpan.Between(Name, CloseChevron); diff --git a/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntaxBase.cs b/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntaxBase.cs new file mode 100644 index 00000000000..f60c97cd8c5 --- /dev/null +++ b/src/Bicep.Core/Syntax/ParameterizedTypeInstantiationSyntaxBase.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bicep.Core.Navigation; +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public abstract class ParameterizedTypeInstantiationSyntaxBase : TypeSyntax, ISymbolReference +{ + public ParameterizedTypeInstantiationSyntaxBase(IdentifierSyntax name, Token openChevron, IEnumerable children, Token closeChevron) + { + AssertTokenType(openChevron, nameof(openChevron), TokenType.LeftChevron); + AssertTokenType(closeChevron, nameof(closeChevron), TokenType.RightChevron); + + this.Name = name; + this.OpenChevron = openChevron; + this.Children = children.ToImmutableArray(); + this.CloseChevron = closeChevron; + this.Arguments = children.OfType().ToImmutableArray(); + } + + public IdentifierSyntax Name { get; } + + public Token OpenChevron { get; } + + public ImmutableArray Children { get; } + + public ImmutableArray Arguments { get; } + + public Token CloseChevron { get; } + + public ParameterizedTypeArgumentSyntax GetArgumentByPosition(int index) => Arguments[index]; + + public override TextSpan Span => TextSpan.Between(Name, CloseChevron); +} diff --git a/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs b/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs index b6b41c9a1ff..b4cff22a41f 100644 --- a/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs +++ b/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs @@ -1139,13 +1139,14 @@ protected virtual SyntaxBase ReplaceCompileTimeImportFromClauseSyntax(CompileTim return new CompileTimeImportFromClauseSyntax(keyword, path); } - void ISyntaxVisitor.VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFromClauseSyntax syntax) => ReplaceCurrent(syntax, ReplaceCompileTimeImportFromClauseSyntax); + void ISyntaxVisitor.VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFromClauseSyntax syntax) + => ReplaceCurrent(syntax, ReplaceCompileTimeImportFromClauseSyntax); protected virtual SyntaxBase ReplaceParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) { var hasChanges = TryRewriteStrict(syntax.Name, out var name); hasChanges |= TryRewriteStrict(syntax.OpenChevron, out var openChevron); - hasChanges |= TryRewriteStrict(syntax.Children, out var children); + hasChanges |= TryRewrite(syntax.Children, out var children); hasChanges |= TryRewriteStrict(syntax.CloseChevron, out var closeChevron); if (!hasChanges) @@ -1155,7 +1156,27 @@ protected virtual SyntaxBase ReplaceParameterizedTypeInstantiationSyntax(Paramet return new ParameterizedTypeInstantiationSyntax(name, openChevron, children, closeChevron); } - void ISyntaxVisitor.VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) => ReplaceCurrent(syntax, ReplaceParameterizedTypeInstantiationSyntax); + void ISyntaxVisitor.VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + => ReplaceCurrent(syntax, ReplaceParameterizedTypeInstantiationSyntax); + + protected virtual SyntaxBase ReplaceInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax) + { + var hasChanges = TryRewrite(syntax.BaseExpression, out var baseExpression); + hasChanges |= TryRewriteStrict(syntax.Dot, out var dot); + hasChanges |= TryRewriteStrict(syntax.PropertyName, out var propertyName); + hasChanges |= TryRewriteStrict(syntax.OpenChevron, out var openChevron); + hasChanges |= TryRewrite(syntax.Children, out var children); + hasChanges |= TryRewriteStrict(syntax.CloseChevron, out var closeChevron); + + if (!hasChanges) + { + return syntax; + } + + return new InstanceParameterizedTypeInstantiationSyntax(baseExpression, dot, propertyName, openChevron, children, closeChevron); + } + void ISyntaxVisitor.VisitInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax) + => ReplaceCurrent(syntax, ReplaceInstanceParameterizedTypeInstantiationSyntax); protected virtual SyntaxBase ReplaceParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) { diff --git a/src/Bicep.Core/Syntax/SyntaxVisitor.cs b/src/Bicep.Core/Syntax/SyntaxVisitor.cs index 9dbfbdea917..cad742bdf75 100644 --- a/src/Bicep.Core/Syntax/SyntaxVisitor.cs +++ b/src/Bicep.Core/Syntax/SyntaxVisitor.cs @@ -149,6 +149,8 @@ public abstract class SyntaxVisitor : ISyntaxVisitor public abstract void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax); + public abstract void VisitInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax); + public abstract void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax); public void Visit(SyntaxBase? node) diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 5bc1410494d..44a28ab0eb3 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -29,7 +29,7 @@ public class DeclaredTypeManager // processed nodes found not to have a declared type will have a null value private readonly ConcurrentDictionary declaredTypes = new(); private readonly ConcurrentDictionary userDefinedTypeReferences = new(); - private readonly ConcurrentDictionary> reifiedTypes = new(); + private readonly ConcurrentDictionary> reifiedTypes = new(); private readonly ITypeManager typeManager; private readonly IBinder binder; private readonly IFeatureProvider features; @@ -48,7 +48,7 @@ public DeclaredTypeManager(TypeManager typeManager, IBinder binder, IFeatureProv public TypeSymbol? GetDeclaredType(SyntaxBase syntax) => this.GetDeclaredTypeAssignment(syntax)?.Reference.Type; - public TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntax syntax) + public TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntaxBase syntax) => GetReifiedTypeResult(syntax).TryUnwrap(); private DeclaredTypeAssignment? GetTypeAssignment(SyntaxBase syntax) @@ -461,7 +461,7 @@ private ITypeReference GetTypeFromTypeSyntax(SyntaxBase syntax, bool allowNamesp SkippedTriviaSyntax => LanguageConstants.Any, ResourceTypeSyntax resource => GetDeclaredType(resource), VariableAccessSyntax typeRef => ConvertTypeExpressionToType(typeRef, allowNamespaceReferences), - ParameterizedTypeInstantiationSyntax parameterizedTypeInvocation => ConvertTypeExpressionToType(parameterizedTypeInvocation), + ParameterizedTypeInstantiationSyntaxBase parameterizedTypeInvocation => ConvertTypeExpressionToType(parameterizedTypeInvocation), ArrayTypeSyntax array => GetDeclaredTypeAssignment(array)?.Reference, ObjectTypeSyntax @object => GetDeclaredType(@object), TupleTypeSyntax tuple => GetDeclaredType(tuple), @@ -532,28 +532,60 @@ private IEnumerable GetValidTypeNames() => binder.NamespaceResolver.GetK .Concat(binder.FileSymbol.ImportedTypes.Select(i => i.Name)) .Distinct(); - private ITypeReference ConvertTypeExpressionToType(ParameterizedTypeInstantiationSyntax syntax) + private ITypeReference ConvertTypeExpressionToType(ParameterizedTypeInstantiationSyntaxBase syntax) => GetReifiedTypeResult(syntax).IsSuccess(out var typeExpression, out var error) ? typeExpression.ExpressedType : ErrorType.Create(error); - private Result GetReifiedTypeResult(ParameterizedTypeInstantiationSyntax syntax) + private Result GetReifiedTypeResult(ParameterizedTypeInstantiationSyntaxBase syntax) => reifiedTypes.GetOrAdd(syntax, InstantiateType); + private Result InstantiateType(ParameterizedTypeInstantiationSyntaxBase syntax) => syntax switch + { + ParameterizedTypeInstantiationSyntax unqualified => InstantiateType(unqualified), + InstanceParameterizedTypeInstantiationSyntax qualified => InstantiateType(qualified), + _ => throw new UnreachableException($"Unrecognized subtype of {nameof(ParameterizedTypeInstantiationSyntaxBase)}: {syntax.GetType().FullName}"), + }; + private Result InstantiateType(ParameterizedTypeInstantiationSyntax syntax) => binder.GetSymbolInfo(syntax) switch { - AmbientTypeSymbol ambientType => InstantiateType(syntax, ambientType, ambientType.Type), - ImportedTypeSymbol importedType => InstantiateType(syntax, importedType, importedType.Type), - TypeAliasSymbol typeAlias => InstantiateType(syntax, typeAlias, typeAlias.Type), + AmbientTypeSymbol ambientType => InstantiateType(syntax, ambientType.Name, ambientType.Type), + ImportedTypeSymbol importedType => InstantiateType(syntax, importedType.Name, importedType.Type), + TypeAliasSymbol typeAlias => InstantiateType(syntax, typeAlias.Name, typeAlias.Type), DeclaredSymbol declaredSymbol => new(DiagnosticBuilder.ForPosition(syntax).ValueSymbolUsedAsType(declaredSymbol.Name)), _ => new(DiagnosticBuilder.ForPosition(syntax).SymbolicNameIsNotAType(syntax.Name.IdentifierName, GetValidTypeNames())), }; - private Result InstantiateType(ParameterizedTypeInstantiationSyntax syntax, Symbol symbol, TypeSymbol symbolType) + private Result InstantiateType(InstanceParameterizedTypeInstantiationSyntax syntax) + { + var baseType = GetTypeFromTypeSyntax(syntax.BaseExpression, allowNamespaceReferences: true).Type; + + if (baseType is ErrorType error && error.GetDiagnostics().FirstOrDefault() is { } baseTypeDiagnostic) + { + return new(baseTypeDiagnostic); + } + + if (baseType is not ObjectType objectType) + { + return new(DiagnosticBuilder.ForPosition(syntax.PropertyName).ObjectRequiredForPropertyAccess(baseType)); + } + + var diagnostics = ToListDiagnosticWriter.Create(); + var propertyType = TypeHelper.GetNamedPropertyType(objectType, syntax.PropertyName, syntax.PropertyName.IdentifierName, shouldWarn: false, diagnostics); + + if (diagnostics.GetDiagnostics().OfType().FirstOrDefault() is { } propertyAccessDiagnostic) + { + return new(propertyAccessDiagnostic); + } + + return InstantiateType(syntax, $"{baseType.Name}.{syntax.PropertyName.IdentifierName}", propertyType); + } + + private Result InstantiateType(ParameterizedTypeInstantiationSyntaxBase syntax, string typeName, TypeSymbol symbolType) => symbolType switch { - TypeTemplate tt => tt.Instantiate(binder, syntax, syntax.Arguments.Select(typeManager.GetTypeInfo)), - _ => new(DiagnosticBuilder.ForPosition(syntax).TypeIsNotParameterizable(symbol.Name)), + TypeTemplate tt => tt.Instantiate(binder, syntax, syntax.Arguments.Select(arg => GetTypeFromTypeSyntax(arg.Expression, allowNamespaceReferences: false).Type)), + _ => new(DiagnosticBuilder.ForPosition(syntax).TypeIsNotParameterizable(typeName)), }; private ITypeReference TypeRefToType(VariableAccessSyntax signifier, TypeAliasSymbol signified) => new DeferredTypeReference(() => diff --git a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs index a6088b2eb16..9e3603c545d 100644 --- a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs +++ b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs @@ -737,9 +737,6 @@ public override void VisitResourceTypeSyntax(ResourceTypeSyntax syntax) return declaredType; }); - public override void VisitParameterizedTypeArgumentSyntax(ParameterizedTypeArgumentSyntax syntax) - => AssignType(syntax, () => typeManager.GetTypeInfo(syntax.Expression)); - private TypeSymbol GetDeclaredTypeAndValidateDecorators(DecorableSyntax targetSyntax, SyntaxBase typeSyntax, IDiagnosticWriter diagnostics) { var declaredType = typeManager.GetDeclaredType(targetSyntax); diff --git a/src/Bicep.Core/TypeSystem/TypeManager.cs b/src/Bicep.Core/TypeSystem/TypeManager.cs index f1005cdad38..6d34c42a206 100644 --- a/src/Bicep.Core/TypeSystem/TypeManager.cs +++ b/src/Bicep.Core/TypeSystem/TypeManager.cs @@ -45,7 +45,7 @@ public IEnumerable GetAllDiagnostics() public Expression? GetMatchedFunctionResultValue(FunctionCallSyntaxBase syntax) => typeAssignmentVisitor.GetMatchedFunctionResultValue(syntax); - public TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntax syntax) + public TypeExpression? TryGetReifiedType(ParameterizedTypeInstantiationSyntaxBase syntax) => declaredTypeManager.TryGetReifiedType(syntax); } } diff --git a/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs index 065e5395ab0..e29d7e6f9fd 100644 --- a/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs +++ b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs @@ -19,7 +19,7 @@ public class TypeTemplate : TypeSymbol { public delegate Result InstantiatorDelegate( IBinder binder, - ParameterizedTypeInstantiationSyntax instantiationSyntax, + ParameterizedTypeInstantiationSyntaxBase instantiationSyntax, ImmutableArray argumentTypes); private readonly InstantiatorDelegate instantiator; From 6cc32a6878d0afd3db8f8482fefe79fc2aae87da Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Wed, 6 Dec 2023 16:51:36 -0500 Subject: [PATCH 10/20] Add missing binder test --- .../ResourceDerivedTypeBinderTests.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs b/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs index ce898c09543..b131930bf54 100644 --- a/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs +++ b/src/Bicep.Core.UnitTests/TypeSystem/ResourceDerivedTypeBinderTests.cs @@ -4,10 +4,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.Features; -using Bicep.Core.Parsing; using Bicep.Core.Resources; using Bicep.Core.Semantics; using Bicep.Core.Semantics.Namespaces; @@ -162,6 +160,25 @@ public void Hydrates_wrapped_types() .Subject.Unwrapped.Should().BeSameAs(hydrated); } + [TestMethod] + public void Hydrates_lambda_types() + { + var hydrated = TypeFactory.CreateBooleanLiteralType(false); + var (sut, unhydratedTypeRef) = SetupBinder(hydrated); + + var containsUnbound = new LambdaType( + ImmutableArray.Create( + TypeFactory.CreateArrayType(new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)), + new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)), + new UnboundResourceDerivedType(unhydratedTypeRef, LanguageConstants.Any)); + + var bound = sut.BindResourceDerivedTypes(containsUnbound).Should().BeOfType().Subject; + bound.ArgumentTypes.Should().SatisfyRespectively( + arg => arg.Type.Should().BeOfType().Subject.Item.Type.Should().BeSameAs(hydrated), + arg => arg.Type.Should().BeSameAs(hydrated)); + bound.ReturnType.Should().BeSameAs(hydrated); + } + private static (ResourceDerivedTypeBinder sut, ResourceTypeReference unhydratedTypeRef) SetupBinder(TypeSymbol hydratedType) { var unhydratedTypeRef = new ResourceTypeReference("type", "version"); From e9eea3883f419e841c1d7ec2a5a55f325d9783a3 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Wed, 6 Dec 2023 17:48:45 -0500 Subject: [PATCH 11/20] Offer completions for the resource utility type --- src/Bicep.Core/LanguageConstants.cs | 1 + .../Namespaces/SystemNamespaceType.cs | 4 +- src/Bicep.Core/Semantics/SymbolHelper.cs | 2 + .../TypeSystem/TypeSymbolValidationFlags.cs | 5 + .../TypeSystem/Types/TypeParameter.cs | 9 +- .../TypeSystem/Types/TypeTemplate.cs | 28 +++- .../CompletionTests.cs | 96 ++++++++++++ .../Completions/BicepCompletionContext.cs | 105 +++++++++---- .../Completions/BicepCompletionContextKind.cs | 5 + .../Completions/BicepCompletionProvider.cs | 144 +++++++++++------- 10 files changed, 306 insertions(+), 93 deletions(-) diff --git a/src/Bicep.Core/LanguageConstants.cs b/src/Bicep.Core/LanguageConstants.cs index 1f20434d9eb..c26bf0c0ff8 100644 --- a/src/Bicep.Core/LanguageConstants.cs +++ b/src/Bicep.Core/LanguageConstants.cs @@ -220,6 +220,7 @@ public static class LanguageConstants public static readonly TypeSymbol StringFilePath = TypeFactory.CreateStringType(validationFlags: TypeSymbolValidationFlags.IsStringFilePath); public static readonly TypeSymbol StringJsonFilePath = TypeFactory.CreateStringType(validationFlags: TypeSymbolValidationFlags.IsStringFilePath | TypeSymbolValidationFlags.IsStringJsonFilePath); public static readonly TypeSymbol StringYamlFilePath = TypeFactory.CreateStringType(validationFlags: TypeSymbolValidationFlags.IsStringFilePath | TypeSymbolValidationFlags.IsStringYamlFilePath); + public static readonly TypeSymbol StringResourceIdentifier = TypeFactory.CreateStringType(validationFlags: TypeSymbolValidationFlags.IsResourceTypeIdentifier); //Type for available loadTextContent encoding diff --git a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs index 4472af3ecbb..2613444e867 100644 --- a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs +++ b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs @@ -1743,7 +1743,9 @@ private static IEnumerable GetBuiltInUtilityTypes(IFeatureProvider if (features.ResourceDerivedTypesEnabled) { yield return new("resource", new TypeTemplate("resource", - ImmutableArray.Create(new TypeParameter("T")), + ImmutableArray.Create(new TypeParameter("ResourceTypeIdentifier", + "A string of the format '@' that identifies the kind of resource whose type definition is to be loaded.", + LanguageConstants.StringResourceIdentifier)), (binder, syntax, argumentTypes) => { if (argumentTypes.FirstOrDefault() is not StringLiteralType stringLiteral) diff --git a/src/Bicep.Core/Semantics/SymbolHelper.cs b/src/Bicep.Core/Semantics/SymbolHelper.cs index a0df63fbf5f..12aeb3c6685 100644 --- a/src/Bicep.Core/Semantics/SymbolHelper.cs +++ b/src/Bicep.Core/Semantics/SymbolHelper.cs @@ -69,6 +69,8 @@ public static class SymbolHelper return null; } + case InstanceParameterizedTypeInstantiationSyntax iptic: + return GetPropertySymbol(getDeclaredTypeFunc(iptic.BaseExpression), iptic.PropertyName.IdentifierName); case PropertyAccessSyntax propertyAccess: { var baseType = getDeclaredTypeFunc(propertyAccess.BaseExpression); diff --git a/src/Bicep.Core/TypeSystem/TypeSymbolValidationFlags.cs b/src/Bicep.Core/TypeSystem/TypeSymbolValidationFlags.cs index c8687d1d687..e0f6dbab4d7 100644 --- a/src/Bicep.Core/TypeSystem/TypeSymbolValidationFlags.cs +++ b/src/Bicep.Core/TypeSystem/TypeSymbolValidationFlags.cs @@ -49,5 +49,10 @@ public enum TypeSymbolValidationFlags /// Indicates that this type will be a String file path to a YAML file and we should offer completions for it where files wih .yaml and .yml extension are prioritised /// IsStringYamlFilePath = 1 << 6, + + /// + /// Indicates that this type will be a string that contains a fully qualified resource type (e.g., 'Microsoft.Resource/deployments@2022-09-01'). + /// + IsResourceTypeIdentifier = 1 << 7, } } diff --git a/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs b/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs index 9a9785c95f8..f17276240ac 100644 --- a/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs +++ b/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs @@ -5,14 +5,7 @@ namespace Bicep.Core.TypeSystem.Types; -public class TypeParameter +public record TypeParameter(string Name, string? Description, TypeSymbol? Type = null, bool Required = true) { - public TypeParameter(string name) - { - Name = name; - } - - public string Name { get; } - public override string ToString() => Name; } diff --git a/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs index e29d7e6f9fd..713141e23c6 100644 --- a/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs +++ b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Bicep.Core.Diagnostics; using Bicep.Core.Intermediate; using Bicep.Core.Parsing; @@ -30,25 +31,44 @@ public TypeTemplate(string name, ImmutableArray parameters, Insta Debug.Assert(!parameters.IsEmpty, "Parameterized types must accept at least one argument."); Parameters = parameters; + MinimumArgumentCount = Parameters.TakeWhile(p => p.Required).Count(); + MaximumArgumentCount = Parameters.Length; this.instantiator = instantiator; } public ImmutableArray Parameters { get; } + public int MinimumArgumentCount { get; } + + public int MaximumArgumentCount { get; } + public override TypeSymbolValidationFlags ValidationFlags => TypeSymbolValidationFlags.PreventAssignment; public override TypeKind TypeKind => TypeKind.TypeReference; public override IEnumerable GetDiagnostics() => ImmutableArray.Empty; - public Result Instantiate(IBinder binder, ParameterizedTypeInstantiationSyntax syntax, IEnumerable argumentTypes) + public TypeParameter? TryGetParameterByIndex(int index) => index < Parameters.Length + ? Parameters[index] + : null; + + public Result Instantiate(IBinder binder, ParameterizedTypeInstantiationSyntaxBase syntax, IEnumerable arguments) { - var argTypesArray = argumentTypes.ToImmutableArray(); + var argTypesArray = arguments.ToImmutableArray(); - if (argTypesArray.Length != Parameters.Length) + if (argTypesArray.Length < MinimumArgumentCount || argTypesArray.Length > MaximumArgumentCount) { return new(DiagnosticBuilder.ForPosition(TextSpan.Between(syntax.OpenChevron, syntax.CloseChevron)) - .ArgumentCountMismatch(argTypesArray.Length, Parameters.Length, Parameters.Length)); + .ArgumentCountMismatch(argTypesArray.Length, MinimumArgumentCount, MaximumArgumentCount)); + } + + for (int i = 0; i < argTypesArray.Length; i++) + { + if (Parameters[i].Type is TypeSymbol argumentBound && !TypeValidator.AreTypesAssignable(argTypesArray[i], argumentBound)) + { + return new(DiagnosticBuilder.ForPosition(syntax.Arguments[i]) + .ArgumentTypeMismatch(argTypesArray[i], argumentBound)); + } } return instantiator.Invoke(binder, syntax, argTypesArray); diff --git a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs index 129c61faa27..23e2a67c184 100644 --- a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs @@ -4544,5 +4544,101 @@ [new Uri("file:///mod2.bicep")] = mod2Content, completions.Should().NotContain(c => c.Label == "bar"); completions.Should().NotContain(c => c.Label == "baz"); } + + [TestMethod] + public async Task Resource_utility_type_offered_as_completion_if_enabled() + { + var mainContent = """ + type acct = | + """; + + var (text, cursors) = ParserHelper.GetFileWithCursors(mainContent, '|'); + Uri mainUri = InMemoryFileResolver.GetFileUri("/path/to/main.bicep"); + + var bicepFile = SourceFileFactory.CreateBicepFile(mainUri, text); + using var helper = await LanguageServerHelper.StartServerWithText( + this.TestContext, + text, + mainUri, + services => services.WithFeatureOverrides(new(ResourceDerivedTypesEnabled: true))); + + var file = new FileRequestHelper(helper.Client, bicepFile); + var completions = await file.RequestCompletion(cursors[0]); + + var updated = file.ApplyCompletion(completions, "resource"); + updated.Should().HaveSourceText(""" + type acct = resource<|> + """); + } + + [TestMethod] + public async Task Resource_types_offered_as_completion_for_single_argument_to_resource_utility_type() + { + var mainContent = """ + type acct = resource + type fullyQualified = sys.resource + """; + + var (text, cursors) = ParserHelper.GetFileWithCursors(mainContent, '|'); + Uri mainUri = InMemoryFileResolver.GetFileUri("/path/to/main.bicep"); + + var bicepFile = SourceFileFactory.CreateBicepFile(mainUri, text); + using var helper = await LanguageServerHelper.StartServerWithText( + this.TestContext, + text, + mainUri, + services => services.WithFeatureOverrides(new(ResourceDerivedTypesEnabled: true))); + + var file = new FileRequestHelper(helper.Client, bicepFile); + + var completions = await file.RequestCompletion(cursors[0]); + var updated = file.ApplyCompletion(completions, "'Microsoft.Storage/storageAccounts'"); + updated.Should().HaveSourceText(""" + type acct = resource<'Microsoft.Storage/storageAccounts@|'> + type fullyQualified = sys.resource + """); + + completions = await file.RequestCompletion(cursors[1]); + updated = file.ApplyCompletion(completions, "'Microsoft.Storage/storageAccounts'"); + updated.Should().HaveSourceText(""" + type acct = resource + type fullyQualified = sys.resource<'Microsoft.Storage/storageAccounts@|'> + """); + } + + [TestMethod] + public async Task Resource_api_versions_offered_as_completion_for_single_argument_to_resource_utility_type_with_resource_type_name_already_filled_in() + { + var mainContent = """ + type acct = resource<'Microsoft.Storage/storageAccounts@|'> + type fullyQualified = sys.resource<'Microsoft.Storage/storageAccounts@|'> + """; + + var (text, cursors) = ParserHelper.GetFileWithCursors(mainContent, '|'); + Uri mainUri = InMemoryFileResolver.GetFileUri("/path/to/main.bicep"); + + var bicepFile = SourceFileFactory.CreateBicepFile(mainUri, text); + using var helper = await LanguageServerHelper.StartServerWithText( + this.TestContext, + text, + mainUri, + services => services.WithFeatureOverrides(new(ResourceDerivedTypesEnabled: true))); + + var file = new FileRequestHelper(helper.Client, bicepFile); + + var completions = await file.RequestCompletion(cursors[0]); + var updated = file.ApplyCompletion(completions, "2022-09-01"); + updated.Should().HaveSourceText(""" + type acct = resource<'Microsoft.Storage/storageAccounts@2022-09-01'|> + type fullyQualified = sys.resource<'Microsoft.Storage/storageAccounts@'> + """); + + completions = await file.RequestCompletion(cursors[1]); + updated = file.ApplyCompletion(completions, "2022-09-01"); + updated.Should().HaveSourceText(""" + type acct = resource<'Microsoft.Storage/storageAccounts@'> + type fullyQualified = sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'|> + """); + } } } diff --git a/src/Bicep.LangServer/Completions/BicepCompletionContext.cs b/src/Bicep.LangServer/Completions/BicepCompletionContext.cs index cd42e05a41d..a6782ff6906 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionContext.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionContext.cs @@ -22,10 +22,7 @@ namespace Bicep.LanguageServer.Completions { public class BicepCompletionContext { - public record FunctionArgumentContext( - FunctionCallSyntaxBase Function, - int ArgumentIndex - ); + public record IndexedSyntaxContext(T Syntax, int ArgumentIndex) where T : SyntaxBase?; private static readonly CompositeSyntaxPattern ExpectingImportSpecification = CompositeSyntaxPattern.Create( cursor: '|', @@ -65,12 +62,13 @@ private BicepCompletionContext( DecorableSyntax? enclosingDecorable, ObjectSyntax? @object, ObjectPropertySyntax? property, - ArraySyntax? array, + IndexedSyntaxContext? array, PropertyAccessSyntax? propertyAccess, ResourceAccessSyntax? resourceAccess, ArrayAccessSyntax? arrayAccess, TargetScopeSyntax? targetScope, - FunctionArgumentContext? functionArgument, + IndexedSyntaxContext? functionArgument, + IndexedSyntaxContext? typeArgument, ImmutableArray activeScopes) { this.Kind = kind; @@ -86,6 +84,7 @@ private BicepCompletionContext( this.ArrayAccess = arrayAccess; this.TargetScope = targetScope; this.FunctionArgument = functionArgument; + this.TypeArgument = typeArgument; this.ActiveScopes = activeScopes; } @@ -99,7 +98,7 @@ private BicepCompletionContext( public ObjectPropertySyntax? Property { get; } - public ArraySyntax? Array { get; } + public IndexedSyntaxContext? Array { get; } public PropertyAccessSyntax? PropertyAccess { get; } @@ -109,7 +108,9 @@ private BicepCompletionContext( public TargetScopeSyntax? TargetScope { get; } - public FunctionArgumentContext? FunctionArgument { get; } + public IndexedSyntaxContext? FunctionArgument { get; } + + public IndexedSyntaxContext? TypeArgument { get; } public ImmutableArray ActiveScopes { get; } @@ -142,7 +143,7 @@ public static BicepCompletionContext Create(IFeatureProvider featureProvider, Co if (previousTrivia is DisableNextLineDiagnosticsSyntaxTrivia) { - return new BicepCompletionContext(BicepCompletionContextKind.DisableNextLineDiagnosticsCodes, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); + return new BicepCompletionContext(BicepCompletionContextKind.DisableNextLineDiagnosticsCodes, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); } } break; @@ -150,18 +151,18 @@ public static BicepCompletionContext Create(IFeatureProvider featureProvider, Co // This will handle the following case: #disable-next-line | if (triviaMatchingOffset.Text.EndsWith(' ')) { - return new BicepCompletionContext(BicepCompletionContextKind.DisableNextLineDiagnosticsCodes, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); + return new BicepCompletionContext(BicepCompletionContextKind.DisableNextLineDiagnosticsCodes, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); } - return new BicepCompletionContext(BicepCompletionContextKind.None, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); + return new BicepCompletionContext(BicepCompletionContextKind.None, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); case SyntaxTriviaType.SingleLineComment when offset > triviaMatchingOffset.Span.Position: case SyntaxTriviaType.MultiLineComment when offset > triviaMatchingOffset.Span.Position && offset < triviaMatchingOffset.Span.Position + triviaMatchingOffset.Span.Length: // we're in a comment, no hints here - return new BicepCompletionContext(BicepCompletionContextKind.None, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); + return new BicepCompletionContext(BicepCompletionContextKind.None, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); } if (IsDisableNextLineDiagnosticsDirectiveStartContext(bicepFile, offset, matchingNodes)) { - return new BicepCompletionContext(BicepCompletionContextKind.DisableNextLineDiagnosticsDirectiveStart, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); + return new BicepCompletionContext(BicepCompletionContextKind.DisableNextLineDiagnosticsDirectiveStart, replacementRange, replacementTarget, null, null, null, null, null, null, null, null, null, null, null, ImmutableArray.Empty); } var topLevelDeclarationInfo = SyntaxMatcher.FindLastNodeOfType(matchingNodes); @@ -176,6 +177,7 @@ public static BicepCompletionContext Create(IFeatureProvider featureProvider, Co var targetScopeInfo = SyntaxMatcher.FindLastNodeOfType(matchingNodes); var activeScopes = ActiveScopesVisitor.GetActiveScopes(compilation.GetEntrypointSemanticModel().Root, offset); var functionArgumentContext = TryGetFunctionArgumentContext(matchingNodes, offset); + var typeArgumentContext = TryGetTypeArgumentContext(matchingNodes, offset); var kind = ConvertFlag(IsTopLevelDeclarationStartContext(matchingNodes, offset), BicepCompletionContextKind.TopLevelDeclarationStart) | ConvertFlag(IsNestedResourceStartContext(matchingNodes, topLevelDeclarationInfo, objectInfo, offset), BicepCompletionContextKind.NestedResourceDeclarationStart) | @@ -204,7 +206,8 @@ public static BicepCompletionContext Create(IFeatureProvider featureProvider, Co ConvertFlag(IsObjectTypePropertyValueContext(matchingNodes, offset), BicepCompletionContextKind.ObjectTypePropertyValue) | ConvertFlag(IsUnionTypeMemberContext(matchingNodes, offset), BicepCompletionContextKind.UnionTypeMember) | ConvertFlag(IsTypedLocalVariableTypeContext(matchingNodes, offset), BicepCompletionContextKind.TypedLocalVariableType) | - ConvertFlag(IsTypedLambdaOutputTypeContext(matchingNodes, offset), BicepCompletionContextKind.TypedLambdaOutputType); + ConvertFlag(IsTypedLambdaOutputTypeContext(matchingNodes, offset), BicepCompletionContextKind.TypedLambdaOutputType) | + ConvertFlag(typeArgumentContext is not null, BicepCompletionContextKind.TypeArgument); if (featureProvider.ExtensibilityEnabled) { @@ -261,12 +264,13 @@ public static BicepCompletionContext Create(IFeatureProvider featureProvider, Co enclosingDecorable.node, objectInfo.node, propertyInfo.node, - arrayInfo.node, + new(arrayInfo.node, arrayInfo.index), propertyAccessInfo.node, resourceAccessInfo.node, arrayAccessInfo.node, targetScopeInfo.node, functionArgumentContext, + typeArgumentContext, activeScopes); } @@ -878,13 +882,13 @@ private static bool IsUnionTypeMemberContext(List matchingNodes, int SyntaxMatcher.IsTailMatch(matchingNodes, union => union.Children.LastOrDefault() is SkippedTriviaSyntax) || SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, _, _, token) => token.Type == TokenType.Identifier); - private static FunctionArgumentContext? TryGetFunctionArgumentContext(List matchingNodes, int offset) + private static IndexedSyntaxContext? TryGetFunctionArgumentContext(List matchingNodes, int offset) { // someFunc(|) // abc.someFunc(|) if (SyntaxMatcher.IsTailMatch(matchingNodes, (func, token) => token == func.OpenParen)) { - return new(Function: (FunctionCallSyntaxBase)matchingNodes[^2], ArgumentIndex: 0); + return new(Syntax: (FunctionCallSyntaxBase)matchingNodes[^2], ArgumentIndex: 0); } // someFunc(x, |) @@ -892,9 +896,8 @@ private static bool IsUnionTypeMemberContext(List matchingNodes, int if (SyntaxMatcher.IsTailMatch(matchingNodes, (func, _) => true)) { var function = (FunctionCallSyntaxBase)matchingNodes[^2]; - var args = function.Arguments.ToImmutableArray(); - return new(Function: function, ArgumentIndex: args.IndexOf((FunctionArgumentSyntax)matchingNodes[^1])); + return new(Syntax: function, ArgumentIndex: function.Arguments.IndexOf((FunctionArgumentSyntax)matchingNodes[^1])); } // someFunc(x,|) @@ -902,10 +905,9 @@ private static bool IsUnionTypeMemberContext(List matchingNodes, int if (SyntaxMatcher.IsTailMatch(matchingNodes, (func, _) => true)) { var function = (FunctionCallSyntaxBase)matchingNodes[^2]; - var args = function.Arguments.ToImmutableArray(); - var previousArg = args.LastOrDefault(x => x.Span.Position < offset); + var previousArg = function.Arguments.LastOrDefault(x => x.Span.Position < offset); - return new(Function: function, ArgumentIndex: previousArg is null ? 0 : (args.IndexOf(previousArg) + 1)); + return new(Syntax: function, ArgumentIndex: previousArg is null ? 0 : (function.Arguments.IndexOf(previousArg) + 1)); } // someFunc(x, 'a|bc') @@ -913,9 +915,8 @@ private static bool IsUnionTypeMemberContext(List matchingNodes, int if (SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, _, token) => token.Type == TokenType.StringComplete)) { var function = (FunctionCallSyntaxBase)matchingNodes[^4]; - var args = function.Arguments.ToImmutableArray(); - return new(Function: function, ArgumentIndex: args.IndexOf((FunctionArgumentSyntax)matchingNodes[^3])); + return new(Syntax: function, ArgumentIndex: function.Arguments.IndexOf((FunctionArgumentSyntax)matchingNodes[^3])); } // someFunc(x, ab|c) @@ -923,9 +924,61 @@ private static bool IsUnionTypeMemberContext(List matchingNodes, int if (SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, _, _, token) => token.Type == TokenType.Identifier)) { var function = (FunctionCallSyntaxBase)matchingNodes[^5]; - var args = function.Arguments.ToImmutableArray(); - return new(Function: function, ArgumentIndex: args.IndexOf((FunctionArgumentSyntax)matchingNodes[^4])); + return new(Syntax: function, ArgumentIndex: function.Arguments.IndexOf((FunctionArgumentSyntax)matchingNodes[^4])); + } + + return null; + } + + private static IndexedSyntaxContext? TryGetTypeArgumentContext(List matchingNodes, int offset) + { + // someType<|> + // abc.someType(|) + if (SyntaxMatcher.IsTailMatch(matchingNodes, (instantiation, token) => token == instantiation.OpenChevron)) + { + return new(Syntax: (ParameterizedTypeInstantiationSyntaxBase)matchingNodes[^2], ArgumentIndex: 0); + } + + // someType + // abc.someType + if (SyntaxMatcher.IsTailMatch(matchingNodes, (instantiation, _) => true)) + { + var instantiation = (ParameterizedTypeInstantiationSyntaxBase)matchingNodes[^2]; + var args = instantiation.Arguments; + + return new(Syntax: instantiation, ArgumentIndex: args.IndexOf((ParameterizedTypeArgumentSyntax)matchingNodes[^1])); + } + + // someType + // abc.someType + if (SyntaxMatcher.IsTailMatch(matchingNodes, (func, _) => true)) + { + var instantiation = (ParameterizedTypeInstantiationSyntaxBase)matchingNodes[^2]; + var args = instantiation.Arguments; + var previousArg = args.LastOrDefault(x => x.Span.Position < offset); + + return new(Syntax: instantiation, ArgumentIndex: previousArg is null ? 0 : (args.IndexOf(previousArg) + 1)); + } + + // someType + // abc.someType + if (SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, _, token) => token.Type == TokenType.StringComplete)) + { + var instantiation = (ParameterizedTypeInstantiationSyntaxBase)matchingNodes[^4]; + var args = instantiation.Arguments; + + return new(Syntax: instantiation, ArgumentIndex: args.IndexOf((ParameterizedTypeArgumentSyntax)matchingNodes[^3])); + } + + // someType + // abc.someType + if (SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, _, _, token) => token.Type == TokenType.Identifier)) + { + var instantiation = (ParameterizedTypeInstantiationSyntaxBase)matchingNodes[^5]; + var args = instantiation.Arguments; + + return new(Syntax: instantiation, ArgumentIndex: args.IndexOf((ParameterizedTypeArgumentSyntax)matchingNodes[^4])); } return null; diff --git a/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs b/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs index 84f515a6bc8..e0a1759cb9f 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs @@ -245,5 +245,10 @@ public enum BicepCompletionContextKind : ulong /// The current location needs a test body. /// TestBody = 1UL << 10, + + /// + /// We're inside the chevrons in a parameterized type: 'typeName<|>' + /// + TypeArgument = 1UL << 45, } } diff --git a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs index a0248ef876e..9ff67a5ec03 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs @@ -94,6 +94,7 @@ public async Task> GetFilteredCompletions(Compilatio .Concat(GetParamIdentifierCompletions(model, context)) .Concat(GetParamValueCompletions(model, context)) .Concat(GetAssertValueCompletions(model, context)) + .Concat(GetTypeArgumentCompletions(model, context)) .Concat(await moduleReferenceCompletionProvider.GetFilteredCompletions(model.SourceFile.FileUri, context, cancellationToken)); } @@ -132,7 +133,7 @@ private IEnumerable GetParamValueCompletions(SemanticModel param var declaredType = paramsSemanticModel.GetDeclaredType(paramAssignment); // loops are not allowed in param files... yet! - return GetValueCompletionsForType(paramsSemanticModel, paramsCompletionContext, declaredType, loopsAllowed: false); + return GetValueCompletionsForType(paramsSemanticModel, paramsCompletionContext, declaredType, paramAssignment.Value, loopsAllowed: false); } private IEnumerable GetDeclarationCompletions(SemanticModel model, BicepCompletionContext context) @@ -250,7 +251,7 @@ private IEnumerable GetDeclarationCompletions(SemanticModel mode private IEnumerable GetTargetScopeCompletions(SemanticModel model, BicepCompletionContext context) { return context.Kind.HasFlag(BicepCompletionContextKind.TargetScope) && context.TargetScope is { } targetScope - ? GetValueCompletionsForType(model, context, model.GetDeclaredType(targetScope), loopsAllowed: false) + ? GetValueCompletionsForType(model, context, model.GetDeclaredType(targetScope), targetScope.Assignment, loopsAllowed: false) : Enumerable.Empty(); } @@ -285,10 +286,8 @@ private IEnumerable GetDeclarationTypeCompletions(SemanticModel { if (context.Kind.HasFlag(BicepCompletionContextKind.ParameterType)) { - var completions = GetAmbientTypeCompletions(model, context) - .Concat(GetParameterTypeSnippets(model.Compilation, context)) - .Concat(GetUserDefinedTypeCompletions(model, context)) - .Concat(GetImportedTypeCompletions(model, context)); + var completions = GetTypeCompletions(model, context) + .Concat(GetParameterTypeSnippets(model.Compilation, context)); // Only show the resource type as a completion if the resource-typed parameter feature is enabled. if (model.Features.ResourceTypedParamsAndOutputsEnabled) @@ -308,9 +307,7 @@ private IEnumerable GetDeclarationTypeCompletions(SemanticModel if (context.Kind.HasFlag(BicepCompletionContextKind.ObjectTypePropertyValue)) { - return GetAmbientTypeCompletions(model, context) - .Concat(GetUserDefinedTypeCompletions(model, context)) - .Concat(GetImportedTypeCompletions(model, context)); + return GetTypeCompletions(model, context); } if (context.Kind.HasFlag(BicepCompletionContextKind.UnionTypeMember)) @@ -323,15 +320,12 @@ private IEnumerable GetDeclarationTypeCompletions(SemanticModel if (context.Kind.HasFlag(BicepCompletionContextKind.TypedLocalVariableType) || context.Kind.HasFlag(BicepCompletionContextKind.TypedLambdaOutputType)) { - // user-defined functions don't yet support user-defined types - return GetAmbientTypeCompletions(model, context); + return GetTypeCompletions(model, context); } if (context.Kind.HasFlag(BicepCompletionContextKind.OutputType)) { - var completions = GetAmbientTypeCompletions(model, context) - .Concat(GetUserDefinedTypeCompletions(model, context)) - .Concat(GetImportedTypeCompletions(model, context)); + var completions = GetTypeCompletions(model, context); // Only show the resource type as a completion if the resource-typed parameter feature is enabled. if (model.Features.ResourceTypedParamsAndOutputsEnabled) @@ -345,6 +339,11 @@ private IEnumerable GetDeclarationTypeCompletions(SemanticModel return Enumerable.Empty(); } + private static IEnumerable GetTypeCompletions(SemanticModel model, BicepCompletionContext context) + => GetAmbientTypeCompletions(model, context) + .Concat(GetUserDefinedTypeCompletions(model, context)) + .Concat(GetImportedTypeCompletions(model, context)); + private static IEnumerable GetAmbientTypeCompletions(SemanticModel model, BicepCompletionContext context) => model.Binder.NamespaceResolver.GetKnownTypes() .ToLookup(ambientType => ambientType.Name) .SelectMany(grouping => grouping.Count() > 1 || model.Binder.FileSymbol.Declarations.Any(decl => LanguageConstants.IdentifierComparer.Equals(grouping.Key, decl.Name)) @@ -426,7 +425,7 @@ private static bool IsTypeLiteralSyntax(SyntaxBase syntax) => syntax is BooleanL _ => null, }; - private IEnumerable GetResourceTypeCompletions(SemanticModel model, BicepCompletionContext context) + private static IEnumerable GetResourceTypeCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ResourceType)) { @@ -464,34 +463,28 @@ private IEnumerable GetResourceTypeCompletions(SemanticModel mod return items; } - static string? TryGetFullyQualifiedType(SyntaxBase? syntax) + return GetResourceTypeCompletions(model, context, context.EnclosingDeclaration switch { - if (syntax is not null && - TryGetEnteredTextFromStringOrSkipped(syntax) is { } entered && - ResourceTypeReference.HasResourceTypePrefix(entered)) - { - return entered; - } - - return null; - } + ResourceDeclarationSyntax resourceSyntax => resourceSyntax.Type, + ParameterDeclarationSyntax parameterSyntax when parameterSyntax.Type is ResourceTypeSyntax resourceType => resourceType.Type, + OutputDeclarationSyntax outputSyntax when outputSyntax.Type is ResourceTypeSyntax resourceType => resourceType.Type, + _ => null, + }); + } - static string? TryGetFullyQualfiedResourceType(SyntaxBase? enclosingDeclaration) - { - return enclosingDeclaration switch - { - ResourceDeclarationSyntax resourceSyntax => TryGetFullyQualifiedType(resourceSyntax.Type), - ParameterDeclarationSyntax parameterSyntax when parameterSyntax.Type is ResourceTypeSyntax resourceType => TryGetFullyQualifiedType(resourceType.Type), - OutputDeclarationSyntax outputSyntax when outputSyntax.Type is ResourceTypeSyntax resourceType => TryGetFullyQualifiedType(resourceType.Type), - _ => null, - }; - } + private static IEnumerable GetResourceTypeCompletions(SemanticModel model, BicepCompletionContext context, SyntaxBase? resourceTypeIdentifierSyntax) + { + var fullyQualifiedResourceType = resourceTypeIdentifierSyntax is not null && + TryGetEnteredTextFromStringOrSkipped(resourceTypeIdentifierSyntax) is string entered && + ResourceTypeReference.HasResourceTypePrefix(entered) + ? entered + : null; // ResourceType completions are divided into 2 parts. // If the current value passes the namespace and type notation ("/") format, we return the fully qualified resource types - if (TryGetFullyQualfiedResourceType(context.EnclosingDeclaration) is string qualified) + if (fullyQualifiedResourceType is not null) { - var resourceType = qualified.Split('@')[0]; + var resourceType = fullyQualifiedResourceType.Split('@')[0]; // newest api versions should be shown first // strict filtering on type so that we show api versions for only the selected type @@ -787,7 +780,7 @@ private IEnumerable GetParameterDefaultValueCompletions(Semantic var declaredType = model.GetDeclaredType(parameter); - return GetValueCompletionsForType(model, context, declaredType, loopsAllowed: false); + return GetValueCompletionsForType(model, context, declaredType, (parameter.Modifier as ParameterDefaultValueSyntax)?.DefaultValue, loopsAllowed: false); } private IEnumerable GetVariableValueCompletions(BicepCompletionContext context) @@ -810,7 +803,7 @@ private IEnumerable GetOutputValueCompletions(SemanticModel mode var declaredType = model.GetDeclaredType(output); - return GetValueCompletionsForType(model, context, declaredType, loopsAllowed: true); + return GetValueCompletionsForType(model, context, declaredType, output.Value, loopsAllowed: true); } private IEnumerable GetOutputTypeFollowerCompletions(BicepCompletionContext context) @@ -930,7 +923,7 @@ private IEnumerable GetAssertValueCompletions(SemanticModel mode return Enumerable.Empty(); } - return GetValueCompletionsForType(model, context, LanguageConstants.Bool, loopsAllowed: false); + return GetValueCompletionsForType(model, context, LanguageConstants.Bool, assert.Value, loopsAllowed: false); } private IEnumerable GetModuleBodyCompletions(SemanticModel model, BicepCompletionContext context) @@ -1306,17 +1299,17 @@ private IEnumerable GetPropertyValueCompletions(SemanticModel mo } var loopsAllowed = context.Property is not null && ForSyntaxValidatorVisitor.IsAddingPropertyLoopAllowed(model, context.Property); - return GetValueCompletionsForType(model, context, declaredTypeAssignment.Reference.Type, loopsAllowed); + return GetValueCompletionsForType(model, context, declaredTypeAssignment.Reference.Type, context.Property?.Value, loopsAllowed); } private IEnumerable GetArrayItemCompletions(SemanticModel model, BicepCompletionContext context) { - if (!context.Kind.HasFlag(BicepCompletionContextKind.ArrayItem)) + if (!context.Kind.HasFlag(BicepCompletionContextKind.ArrayItem) || context.Array?.Syntax is not ArraySyntax arraySyntax) { return Enumerable.Empty(); } - var declaredTypeAssignment = GetDeclaredTypeAssignment(model, context.Array); + var declaredTypeAssignment = GetDeclaredTypeAssignment(model, arraySyntax); if (declaredTypeAssignment?.Reference.Type is not ArrayType arrayType) { return Enumerable.Empty(); @@ -1330,23 +1323,50 @@ private IEnumerable GetArrayItemCompletions(SemanticModel model, return Enumerable.Empty(); } - return GetValueCompletionsForType(model, context, arrayType.Item.Type, loopsAllowed: false); + return GetValueCompletionsForType(model, context, arrayType.Item.Type, arraySyntax.Items.Skip(context.Array.ArgumentIndex).FirstOrDefault(), loopsAllowed: false); } private IEnumerable GetFunctionParamCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.FunctionArgument) || context.FunctionArgument is not { } functionArgument - || model.GetSymbolInfo(functionArgument.Function) is not IFunctionSymbol functionSymbol) + || model.GetSymbolInfo(functionArgument.Syntax) is not IFunctionSymbol functionSymbol) { return Enumerable.Empty(); } var argType = functionSymbol.GetDeclaredArgumentType(functionArgument.ArgumentIndex); - return GetValueCompletionsForType(model, context, argType, loopsAllowed: false); + return GetValueCompletionsForType(model, context, argType, functionArgument.Syntax.Arguments.Skip(functionArgument.ArgumentIndex).FirstOrDefault(), loopsAllowed: false); + } + + private IEnumerable GetTypeArgumentCompletions(SemanticModel model, BicepCompletionContext context) + { + if (!context.Kind.HasFlag(BicepCompletionContextKind.TypeArgument) || + context.TypeArgument is not { } typeArgument || + GetSymbolType(model.GetSymbolInfo(typeArgument.Syntax)) is not TypeTemplate typeTemplate || + typeTemplate.Parameters.Length <= typeArgument.ArgumentIndex) + { + return Enumerable.Empty(); + } + + return typeTemplate.Parameters[typeArgument.ArgumentIndex].Type is TypeSymbol type + ? GetValueCompletionsForType(model, + context, + type, + typeArgument.Syntax.Arguments.Skip(typeArgument.ArgumentIndex).Select(arg => arg.Expression).FirstOrDefault(), + loopsAllowed: false) + : GetTypeCompletions(model, context); } + private static TypeSymbol? GetSymbolType(Symbol? symbol) => symbol switch + { + ITypeReference typeReference => typeReference.Type, + DeclaredSymbol declared => declared.Type, + PropertySymbol property => property.Type, + _ => null, + }; + private IEnumerable GetFileCompletionPaths(SemanticModel model, BicepCompletionContext context, TypeSymbol argType) { if (context.FunctionArgument is not { } functionArgument || !argType.ValidationFlags.HasFlag(TypeSymbolValidationFlags.IsStringFilePath)) @@ -1356,7 +1376,7 @@ private IEnumerable GetFileCompletionPaths(SemanticModel model, //try get entered text. we need to provide path completions when something else than string is entered and in that case we use the token value to get what's currently entered //(token value for string will have single quotes, so we need to avoid it) - var entered = (functionArgument.Function.Arguments.ElementAtOrDefault(functionArgument.ArgumentIndex)?.Expression as StringSyntax)?.TryGetLiteralValue() ?? string.Empty; + var entered = (functionArgument.Syntax.Arguments.ElementAtOrDefault(functionArgument.ArgumentIndex)?.Expression as StringSyntax)?.TryGetLiteralValue() ?? string.Empty; // These should only fail if we're not able to resolve cwd path or the entered string if (TryGetFilesForPathCompletions(model.SourceFile.FileUri, entered) is not { } fileCompletionInfo) @@ -1389,7 +1409,7 @@ private IEnumerable GetFileCompletionPaths(SemanticModel model, return fileItems.Concat(dirItems); } - private IEnumerable GetValueCompletionsForType(SemanticModel model, BicepCompletionContext context, TypeSymbol? type, bool loopsAllowed) + private IEnumerable GetValueCompletionsForType(SemanticModel model, BicepCompletionContext context, TypeSymbol? type, SyntaxBase? currentValue, bool loopsAllowed) { var replacementRange = context.ReplacementRange; switch (type) @@ -1407,6 +1427,13 @@ private IEnumerable GetValueCompletionsForType(SemanticModel mod } break; + case StringType when type.ValidationFlags.HasFlag(TypeSymbolValidationFlags.IsResourceTypeIdentifier): + foreach (var completion in GetResourceTypeCompletions(model, context, currentValue)) + { + yield return completion; + } + break; + case StringLiteralType stringLiteral: yield return CompletionItemBuilder.Create(CompletionItemKind.EnumMember, stringLiteral.Name) .WithPlainTextEdit(replacementRange, stringLiteral.Name) @@ -1467,7 +1494,7 @@ private IEnumerable GetValueCompletionsForType(SemanticModel mod break; case UnionType union: - var aggregatedCompletions = union.Members.SelectMany(typeRef => GetValueCompletionsForType(model, context, typeRef.Type, loopsAllowed)); + var aggregatedCompletions = union.Members.SelectMany(typeRef => GetValueCompletionsForType(model, context, typeRef.Type, currentValue, loopsAllowed)); foreach (var completion in aggregatedCompletions) { yield return completion; @@ -1706,12 +1733,21 @@ private static CompletionItem CreateKeywordCompletion(string keyword, string det .WithSortText(GetSortText(keyword, priority)) .Build(); - private static CompletionItem CreateTypeCompletion(string typeName, AmbientTypeSymbol type, Range replacementRange, CompletionPriority priority = CompletionPriority.Medium) => - CompletionItemBuilder.Create(CompletionItemKind.Class, typeName) + private static CompletionItem CreateTypeCompletion(string typeName, AmbientTypeSymbol type, Range replacementRange, CompletionPriority priority = CompletionPriority.Medium) + { + var builder = CompletionItemBuilder.Create(CompletionItemKind.Class, typeName) .WithPlainTextEdit(replacementRange, typeName) .WithDetail(type.Description ?? type.Name) - .WithSortText(GetSortText(typeName, priority)) - .Build(); + .WithSortText(GetSortText(typeName, priority)); + + builder = type.Type switch + { + TypeTemplate => builder.WithSnippetEdit(replacementRange, $"{typeName}<$0>"), + _ => builder.WithPlainTextEdit(replacementRange, typeName), + }; + + return builder.Build(); + } private static CompletionItem CreateDeclaredTypeCompletion(SemanticModel model, TypeAliasSymbol declaredType, Range replacementRange, CompletionPriority priority = CompletionPriority.Medium) { @@ -1991,7 +2027,7 @@ private IEnumerable GetProviderImportCompletions(SemanticModel m model.GetSymbolInfo(importSyntax) is ProviderNamespaceSymbol providerSymbol && providerSymbol.TryGetNamespaceType() is { } namespaceType) { - foreach (var completion in GetValueCompletionsForType(model, context, namespaceType.ConfigurationType, loopsAllowed: false)) + foreach (var completion in GetValueCompletionsForType(model, context, namespaceType.ConfigurationType, importSyntax.Config, loopsAllowed: false)) { yield return completion; } From 4f34752d9ff2f1a7c8343500b599e5ac42fbf141 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Mon, 11 Dec 2023 11:13:57 -0500 Subject: [PATCH 12/20] Add signature help support for parameterized types --- .../Namespaces/SystemNamespaceType.cs | 38 +++-- .../TypeSystem/Types/TypeParameter.cs | 32 +++- .../TypeSystem/Types/TypeTemplate.cs | 5 +- .../SignatureHelpTests.cs | 19 +++ .../Completions/BicepCompletionProvider.cs | 13 +- .../Handlers/BicepSignatureHelpHandler.cs | 158 +++++++++++++++--- 6 files changed, 221 insertions(+), 44 deletions(-) diff --git a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs index 2cc1c9d82af..344372e9c5d 100644 --- a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs +++ b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs @@ -1743,25 +1743,31 @@ private static IEnumerable GetBuiltInUtilityTypes(IFeatureProvider { if (features.ResourceDerivedTypesEnabled) { - yield return new("resource", new TypeTemplate("resource", - ImmutableArray.Create(new TypeParameter("ResourceTypeIdentifier", - "A string of the format '@' that identifies the kind of resource whose type definition is to be loaded.", - LanguageConstants.StringResourceIdentifier)), - (binder, syntax, argumentTypes) => - { - if (argumentTypes.FirstOrDefault() is not StringLiteralType stringLiteral) + yield return new("resource", + new TypeTemplate("resource", + ImmutableArray.Create(new TypeParameter("ResourceTypeIdentifier", + "A string of the format '@' that identifies the kind of resource whose body type definition is to be used.", + LanguageConstants.StringResourceIdentifier)), + (binder, syntax, argumentTypes) => { - return new(DiagnosticBuilder.ForPosition(TextSpan.BetweenExclusive(syntax.OpenChevron, syntax.CloseChevron)).CompileTimeConstantRequired()); - } + if (argumentTypes.FirstOrDefault() is not StringLiteralType stringLiteral) + { + return new(DiagnosticBuilder.ForPosition(TextSpan.BetweenExclusive(syntax.OpenChevron, syntax.CloseChevron)).CompileTimeConstantRequired()); + } - if (!TypeHelper.GetResourceTypeFromString(binder, stringLiteral.RawStringValue, ResourceTypeGenerationFlags.None, parentResourceType: null) - .IsSuccess(out var resourceType, out var errorBuilder)) - { - return new(errorBuilder(DiagnosticBuilder.ForPosition(syntax.GetArgumentByPosition(0)))); - } + if (!TypeHelper.GetResourceTypeFromString(binder, stringLiteral.RawStringValue, ResourceTypeGenerationFlags.None, parentResourceType: null) + .IsSuccess(out var resourceType, out var errorBuilder)) + { + return new(errorBuilder(DiagnosticBuilder.ForPosition(syntax.GetArgumentByPosition(0)))); + } - return new(new ResourceDerivedTypeExpression(syntax, resourceType, resourceType.Body.Type)); - })); + return new(new ResourceDerivedTypeExpression(syntax, resourceType, resourceType.Body.Type)); + }), + description: """ + Use the type definition of the body of a specific resource rather than a user-defined type. + + NB: The type definition will be checked by Bicep when the template is compiled but will not be enforced by the ARM engine during a deployment. + """); } } diff --git a/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs b/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs index f17276240ac..9bea23d3b99 100644 --- a/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs +++ b/src/Bicep.Core/TypeSystem/Types/TypeParameter.cs @@ -1,11 +1,41 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections.Generic; +using System.Text; +using System.Threading; using Bicep.Core.Diagnostics; namespace Bicep.Core.TypeSystem.Types; -public record TypeParameter(string Name, string? Description, TypeSymbol? Type = null, bool Required = true) +public record TypeParameter(string Name, string Description, TypeSymbol? Type = null, bool Required = true) { + private readonly Lazy lazySignature = new( + () => + { + StringBuilder builder = new(); + if (!Required) + { + builder.Append('['); + } + + builder.Append(Name); + + if (Type is not null) + { + builder.Append(": ").Append(Type); + } + + if (!Required) + { + builder.Append(']'); + } + + return builder.ToString(); + }, + LazyThreadSafetyMode.PublicationOnly); + public override string ToString() => Name; + + public string Signature => lazySignature.Value; } diff --git a/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs index 713141e23c6..8d2aea2d633 100644 --- a/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs +++ b/src/Bicep.Core/TypeSystem/Types/TypeTemplate.cs @@ -26,16 +26,19 @@ public delegate Result InstantiatorDelegate( private readonly InstantiatorDelegate instantiator; public TypeTemplate(string name, ImmutableArray parameters, InstantiatorDelegate instantiator) - : base($"Type<{name}<{string.Join(", ", parameters)}>>") + : base($"{name}<{string.Join(", ", parameters)}>") { Debug.Assert(!parameters.IsEmpty, "Parameterized types must accept at least one argument."); + UnparameterizedName = name; Parameters = parameters; MinimumArgumentCount = Parameters.TakeWhile(p => p.Required).Count(); MaximumArgumentCount = Parameters.Length; this.instantiator = instantiator; } + public string UnparameterizedName { get; } + public ImmutableArray Parameters { get; } public int MinimumArgumentCount { get; } diff --git a/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs b/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs index 12be7b1033e..a14c834ff33 100644 --- a/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs @@ -163,6 +163,25 @@ func isTrue(input bool) bool => !(input == false) signature.Documentation!.MarkupContent!.Value.Should().Be("Checks whether the input is true in a roundabout way"); } + [TestMethod] + public async Task Signature_help_works_with_parameterized_types() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(@"type resourceDerived = resource<|>"); + + using var server = await MultiFileLanguageServerHelper.StartLanguageServer(TestContext, services => services.WithFeatureOverrides(new(ResourceDerivedTypesEnabled: true))); + var file = await new ServerRequestHelper(TestContext, server).OpenFile(text); + + var signatureHelp = await file.RequestSignatureHelp(cursor); + var signature = signatureHelp!.Signatures.Single(); + + signature.Label.Should().Be("resource"); + signature.Documentation!.MarkupContent!.Value.Should().Be(""" + Use the type definition of the body of a specific resource rather than a user-defined type. + + NB: The type definition will be checked by Bicep when the template is compiled but will not be enforced by the ARM engine during a deployment. + """); + } + private static async Task ValidateOffset(ILanguageClient client, DocumentUri uri, BicepSourceFile bicepFile, int offset, IFunctionSymbol? symbol, bool expectDecorator) { var position = PositionHelper.GetPosition(bicepFile.LineStarts, offset); diff --git a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs index 9ff67a5ec03..ff876a4a071 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs @@ -1740,11 +1740,16 @@ private static CompletionItem CreateTypeCompletion(string typeName, AmbientTypeS .WithDetail(type.Description ?? type.Name) .WithSortText(GetSortText(typeName, priority)); - builder = type.Type switch + if (type.Type is TypeTemplate) { - TypeTemplate => builder.WithSnippetEdit(replacementRange, $"{typeName}<$0>"), - _ => builder.WithPlainTextEdit(replacementRange, typeName), - }; + builder = builder.WithSnippetEdit(replacementRange, $"{typeName}<$0>") + // parameterized types always require at least one argument, so automatically request signature help + .WithCommand(new Command { Name = EditorCommands.SignatureHelp, Title = "signature help" }); + } + else + { + builder = builder.WithPlainTextEdit(replacementRange, typeName); + } return builder.Build(); } diff --git a/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs b/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs index 4344aa7a372..fbb5c842e34 100644 --- a/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs @@ -28,6 +28,8 @@ public class BicepSignatureHelpHandler : SignatureHelpHandlerBase { private const string FunctionArgumentStart = "("; private const string FunctionArgumentEnd = ")"; + private const string TypeArgumentsStart = "<"; + private const string TypeArgumentsEnd = ">"; private readonly ICompilationManager compilationManager; @@ -39,7 +41,6 @@ public BicepSignatureHelpHandler(ICompilationManager compilationManager) public override Task Handle(SignatureHelpParams request, CancellationToken cancellationToken) { // local function - static Task NoHelp() => Task.FromResult(null); CompilationContext? context = this.compilationManager.GetCompilation(request.TextDocument.Uri); if (context == null) @@ -49,12 +50,53 @@ public BicepSignatureHelpHandler(ICompilationManager compilationManager) int offset = PositionHelper.GetOffset(context.LineStarts, request.Position); - var functionCall = GetActiveFunctionCall(context.ProgramSyntax, offset); - if (functionCall == null) + return GetActiveSyntaxInNeedOfSignatureHelp(context.ProgramSyntax, offset) switch { - return NoHelp(); + FunctionCallSyntaxBase functionCall + => Handle(context, functionCall, offset, request), + ParameterizedTypeInstantiationSyntaxBase typeInstantiation + => Handle(context, typeInstantiation, offset), + _ => NoHelp(), + }; + } + + private static Task NoHelp() => Task.FromResult(null); + + private static SyntaxBase? GetActiveSyntaxInNeedOfSignatureHelp(ProgramSyntax syntax, int offset) + { + // if the cursor is placed after the closing paren of a function, it needs to count as outside of that function call + // for purposes of signature help (otherwise we'll show the wrong function when function calls are nested) + var matchingNodes = SyntaxMatcher.FindNodesMatchingOffsetExclusive(syntax, offset); + + var functionCallIndex = matchingNodes + .FindLastIndex( + matchingNodes.Count - 1, + current => current is FunctionCallSyntaxBase functionCall && TextSpan.BetweenExclusive(functionCall.OpenParen.Span, functionCall.CloseParen).ContainsInclusive(offset)); + + if (functionCallIndex >= 0) + { + return matchingNodes[functionCallIndex]; } + var parameterizedTypeInstantiationIndex = matchingNodes + .FindLastIndex( + matchingNodes.Count - 1, + current => current is ParameterizedTypeInstantiationSyntaxBase typeInstantiation && + TextSpan.BetweenExclusive(typeInstantiation.OpenChevron.Span, typeInstantiation.CloseChevron).ContainsInclusive(offset)); + + if (parameterizedTypeInstantiationIndex >= 0) + { + return matchingNodes[parameterizedTypeInstantiationIndex]; + } + + return null; + } + + private static Task Handle(CompilationContext context, + FunctionCallSyntaxBase functionCall, + int offset, + SignatureHelpParams request) + { var semanticModel = context.Compilation.GetEntrypointSemanticModel(); var symbol = semanticModel.GetSymbolInfo(functionCall); if (symbol is not IFunctionSymbol functionSymbol) @@ -65,8 +107,7 @@ public BicepSignatureHelpHandler(ICompilationManager compilationManager) // suppress ErrorType in arguments because the code is being written // this prevents function signature mismatches due to errors - var arguments = functionCall.Arguments.ToImmutableArray(); - var normalizedArgumentTypes = NormalizeArgumentTypes(arguments, semanticModel); + var normalizedArgumentTypes = NormalizeArgumentTypes(functionCall.Arguments, semanticModel); // do not include return type in signatures for decorator functions // because the return type on decorators is currently an internal implementation detail @@ -74,26 +115,12 @@ public BicepSignatureHelpHandler(ICompilationManager compilationManager) // (can revisit if we add decorator extensibility in the future) var includeReturnType = semanticModel.Binder.GetParent(functionCall) is not DecoratorSyntax; - var signatureHelp = CreateSignatureHelp(arguments, normalizedArgumentTypes, functionSymbol, offset, includeReturnType); + var signatureHelp = CreateSignatureHelp(functionCall.Arguments, normalizedArgumentTypes, functionSymbol, offset, includeReturnType); signatureHelp = TryReuseActiveSignature(request.Context, signatureHelp); return Task.FromResult(signatureHelp); } - private static FunctionCallSyntaxBase? GetActiveFunctionCall(ProgramSyntax syntax, int offset) - { - // if the cursor is placed after the closing paren of a function, it needs to count as outside of that function call - // for purposes of signature help (otherwise we'll show the wrong function when function calls are nested) - var matchingNodes = SyntaxMatcher.FindNodesMatchingOffsetExclusive(syntax, offset); - - var index = matchingNodes - .FindLastIndex( - matchingNodes.Count - 1, - current => current is FunctionCallSyntaxBase functionCall && TextSpan.BetweenExclusive(functionCall.OpenParen.Span, functionCall.CloseParen).ContainsInclusive(offset)); - - return index < 0 ? null : (FunctionCallSyntaxBase)matchingNodes[index]; - } - private static SignatureHelp TryReuseActiveSignature(SignatureHelpContext? context, SignatureHelp signatureHelp) { if (context?.ActiveSignatureHelp == null || @@ -262,6 +289,91 @@ private static void AppendParameter(StringBuilder typeSignature, List Handle(CompilationContext context, + ParameterizedTypeInstantiationSyntaxBase typeInstantiation, + int offset) + { + var semanticModel = context.Compilation.GetEntrypointSemanticModel(); + var symbol = semanticModel.GetSymbolInfo(typeInstantiation); + if (GetSymbolType(symbol) is not TypeTemplate parameterizable) + { + // no symbol or symbol type is not parameterizable + return NoHelp(); + } + + var documentation = symbol switch + { + AmbientTypeSymbol ambientType => ambientType.Description, + _ => null, + }; + + return Task.FromResult(CreateSignatureHelp(parameterizable, documentation, typeInstantiation.Arguments, offset)); + } + + private static TypeSymbol? GetSymbolType(Symbol? symbol) => symbol switch + { + ITypeReference typeReference => typeReference.Type, + DeclaredSymbol declared => declared.Type, + PropertySymbol property => property.Type, + _ => null, + }; + + private static SignatureHelp CreateSignatureHelp(TypeTemplate typeTemplate, string? documentation, ImmutableArray arguments, int offset) + { + return new SignatureHelp + { + Signatures = new Container(CreateSignature(typeTemplate, documentation)), + ActiveSignature = 0, + ActiveParameter = GetActiveParameterIndex(arguments, offset) + }; + } + + private static int? GetActiveParameterIndex(ImmutableArray arguments, int offset) + { + for (int i = 0; i < arguments.Length; i++) + { + // the comma token is included in the argument node, so we need to check the span of the expression + if (arguments[i].Expression.Span.ContainsInclusive(offset)) + { + return i; + } + } + + return null; + } + + private static SignatureInformation CreateSignature(TypeTemplate typeTemplate, string? documentation) + { + const string delimiter = ", "; + + var typeSignature = new StringBuilder(); + var parameters = new List(); + + typeSignature.Append(typeTemplate.UnparameterizedName); + typeSignature.Append(TypeArgumentsStart); + + for (int i = 0; i < typeTemplate.Parameters.Length; i++) + { + if (i > 0) + { + typeSignature.Append(delimiter); + } + + AppendParameter(typeSignature, parameters, typeTemplate.Parameters[i].Signature, typeTemplate.Parameters[i].Description); + } + + typeSignature.Append(TypeArgumentsEnd); + + return new SignatureInformation + { + Label = typeSignature.ToString(), + Documentation = documentation is not null + ? new MarkupContent { Kind = MarkupKind.Markdown, Value = documentation } + : null, + Parameters = new Container(parameters) + }; + } + protected override SignatureHelpRegistrationOptions CreateRegistrationOptions(SignatureHelpCapability capability, ClientCapabilities clientCapabilities) => new() { DocumentSelector = DocumentSelectorFactory.CreateForBicepAndParams(), @@ -269,8 +381,10 @@ private static void AppendParameter(StringBuilder typeSignature, List - triggers sig. help for the outer parameterized type (or nothing) */ - TriggerCharacters = new Container(FunctionArgumentStart, ",", FunctionArgumentEnd), + TriggerCharacters = new Container(FunctionArgumentStart, ",", FunctionArgumentEnd, TypeArgumentsStart, TypeArgumentsEnd), RetriggerCharacters = new Container() }; } From 8b1cfcff7ab6d29e03dea23fd7a577aecd2eb62c Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Thu, 4 Jan 2024 16:26:16 -0500 Subject: [PATCH 13/20] Correct merge issue --- src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 99f2445f6b4..5caa66a1847 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -2093,11 +2093,6 @@ public FixableDiagnostic ProviderDeclarationViaImportKeywordIsDeprecated(Provide codeFix); } - public ErrorDiagnostic MalformedProviderPackage(string ociManifestPath) => new( - TextSpan, - "BCP382", - $"The provider package is malformed and could not be loaded from \"{ociManifestPath}\"."); - public ErrorDiagnostic TypeIsNotParameterizable(string typeName) => new( TextSpan, "BCP383", From 121685ea359c587120fa54dde040b2ddf4514fa7 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Thu, 4 Jan 2024 17:26:21 -0500 Subject: [PATCH 14/20] Add baseline --- src/Bicep.Core.Samples/DataSets.cs | 2 + .../ResourceDerivedTypes_LF/bicepconfig.json | 5 + .../ResourceDerivedTypes_LF/main.bicep | 16 +++ .../main.diagnostics.bicep | 18 +++ .../main.formatted.bicep | 16 +++ .../ResourceDerivedTypes_LF/main.ir.bicep | 47 +++++++ .../ResourceDerivedTypes_LF/main.json | 50 +++++++ .../ResourceDerivedTypes_LF/main.pprint.bicep | 18 +++ .../main.sourcemap.bicep | 48 +++++++ .../main.symbolicnames.json | 50 +++++++ .../main.symbols.bicep | 20 +++ .../ResourceDerivedTypes_LF/main.syntax.bicep | 123 ++++++++++++++++++ .../ResourceDerivedTypes_LF/main.tokens.bicep | 80 ++++++++++++ 13 files changed, 493 insertions(+) create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/bicepconfig.json create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep diff --git a/src/Bicep.Core.Samples/DataSets.cs b/src/Bicep.Core.Samples/DataSets.cs index 8dc6a453530..ab4f5b17755 100644 --- a/src/Bicep.Core.Samples/DataSets.cs +++ b/src/Bicep.Core.Samples/DataSets.cs @@ -74,6 +74,8 @@ public static class DataSets public static DataSet ResourcesManagementGroup_CRLF => CreateDataSet(); + public static DataSet ResourceDerivedTypes_LF => CreateDataSet(); + public static DataSet ResourcesTenant_CRLF => CreateDataSet(); public static DataSet TypeDeclarations_LF => CreateDataSet(); diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/bicepconfig.json b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/bicepconfig.json new file mode 100644 index 00000000000..f532743d2ac --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/bicepconfig.json @@ -0,0 +1,5 @@ +{ + "experimentalFeaturesEnabled": { + "resourceDerivedTypes": true + } +} diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep new file mode 100644 index 00000000000..bd6d59223ad --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep @@ -0,0 +1,16 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { + name: 'default' + properties: { + tags: { + fizz: 'buzz' + snap: 'crackle' + } + } +} + +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { + name: 'myId' + location: 'eastus' +} diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep new file mode 100644 index 00000000000..5745cee9feb --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep @@ -0,0 +1,18 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { +//@[6:9) [no-unused-params (Warning)] Parameter "bar" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-params)) |bar| + name: 'default' + properties: { + tags: { + fizz: 'buzz' + snap: 'crackle' + } + } +} + +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { + name: 'myId' + location: 'eastus' +} + diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep new file mode 100644 index 00000000000..d1b2ff13b3b --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep @@ -0,0 +1,16 @@ +type foo = resource < 'Microsoft.Storage/storageAccounts@2023-01-01' > + +param bar resource < 'Microsoft.Resources/tags@2022-09-01' > = { + name: 'default' + properties: { + tags: { + fizz: 'buzz' + snap: 'crackle' + } + } +} + +output baz resource < 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' > = { + name: 'myId' + location: 'eastus' +} diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep new file mode 100644 index 00000000000..ecd5aa0d9d6 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep @@ -0,0 +1,47 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[00:356) ProgramExpression +//@[00:067) ├─DeclaredTypeExpression { Name = foo } +//@[11:067) | └─ResourceDerivedTypeExpression { Name = Microsoft.Storage/storageAccounts } + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { +//@[00:160) ├─DeclaredParameterExpression { Name = bar } +//@[10:057) | ├─ResourceDerivedTypeExpression { Name = Microsoft.Resources/tags } +//@[60:160) | └─ObjectExpression + name: 'default' +//@[02:017) | ├─ObjectPropertyExpression +//@[02:006) | | ├─StringLiteralExpression { Value = name } +//@[08:017) | | └─StringLiteralExpression { Value = default } + properties: { +//@[02:078) | └─ObjectPropertyExpression +//@[02:012) | ├─StringLiteralExpression { Value = properties } +//@[14:078) | └─ObjectExpression + tags: { +//@[04:058) | └─ObjectPropertyExpression +//@[04:008) | ├─StringLiteralExpression { Value = tags } +//@[10:058) | └─ObjectExpression + fizz: 'buzz' +//@[06:018) | ├─ObjectPropertyExpression +//@[06:010) | | ├─StringLiteralExpression { Value = fizz } +//@[12:018) | | └─StringLiteralExpression { Value = buzz } + snap: 'crackle' +//@[06:021) | └─ObjectPropertyExpression +//@[06:010) | ├─StringLiteralExpression { Value = snap } +//@[12:021) | └─StringLiteralExpression { Value = crackle } + } + } +} + +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { +//@[00:124) └─DeclaredOutputExpression { Name = baz } +//@[11:082) ├─ResourceDerivedTypeExpression { Name = Microsoft.ManagedIdentity/userAssignedIdentities } +//@[85:124) └─ObjectExpression + name: 'myId' +//@[02:014) ├─ObjectPropertyExpression +//@[02:006) | ├─StringLiteralExpression { Value = name } +//@[08:014) | └─StringLiteralExpression { Value = myId } + location: 'eastus' +//@[02:020) └─ObjectPropertyExpression +//@[02:010) ├─StringLiteralExpression { Value = location } +//@[12:020) └─StringLiteralExpression { Value = eastus } +} + diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json new file mode 100644 index 00000000000..10aeb05b1b1 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "dev", + "templateHash": "12442568148038291906" + } + }, + "definitions": { + "foo": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + } + }, + "parameters": { + "bar": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Resources/tags@2022-09-01" + }, + "defaultValue": { + "name": "default", + "properties": { + "tags": { + "fizz": "buzz", + "snap": "crackle" + } + } + } + } + }, + "resources": {}, + "outputs": { + "baz": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31" + }, + "value": { + "name": "myId", + "location": "eastus" + } + } + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep new file mode 100644 index 00000000000..4f35e469cfe --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep @@ -0,0 +1,18 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { + name: 'default' + properties: { + tags: { + fizz: 'buzz' + snap: 'crackle' + } + } +} + +output baz resource< + 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' +> = { + name: 'myId' + location: 'eastus' +} diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep new file mode 100644 index 00000000000..1b9ed06445e --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep @@ -0,0 +1,48 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@ "foo": { +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" +//@ } +//@ } + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { +//@ "bar": { +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.Resources/tags@2022-09-01" +//@ }, +//@ "defaultValue": { +//@ } +//@ } + name: 'default' +//@ "name": "default", + properties: { +//@ "properties": { +//@ } + tags: { +//@ "tags": { +//@ } + fizz: 'buzz' +//@ "fizz": "buzz", + snap: 'crackle' +//@ "snap": "crackle" + } + } +} + +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { +//@ "baz": { +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31" +//@ }, +//@ "value": { +//@ } +//@ } + name: 'myId' +//@ "name": "myId", + location: 'eastus' +//@ "location": "eastus" +} + diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json new file mode 100644 index 00000000000..10aeb05b1b1 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "dev", + "templateHash": "12442568148038291906" + } + }, + "definitions": { + "foo": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + } + }, + "parameters": { + "bar": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Resources/tags@2022-09-01" + }, + "defaultValue": { + "name": "default", + "properties": { + "tags": { + "fizz": "buzz", + "snap": "crackle" + } + } + } + } + }, + "resources": {}, + "outputs": { + "baz": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31" + }, + "value": { + "name": "myId", + "location": "eastus" + } + } + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep new file mode 100644 index 00000000000..350f761c3f9 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep @@ -0,0 +1,20 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[5:08) TypeAlias foo. Type: Type. Declaration start char: 0, length: 67 + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { +//@[6:09) Parameter bar. Type: Microsoft.Resources/tags. Declaration start char: 0, length: 160 + name: 'default' + properties: { + tags: { + fizz: 'buzz' + snap: 'crackle' + } + } +} + +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { +//@[7:10) Output baz. Type: Microsoft.ManagedIdentity/userAssignedIdentities. Declaration start char: 0, length: 124 + name: 'myId' + location: 'eastus' +} + diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep new file mode 100644 index 00000000000..f05f87c4c41 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep @@ -0,0 +1,123 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[00:356) ProgramSyntax +//@[00:067) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:008) | ├─IdentifierSyntax +//@[05:008) | | └─Token(Identifier) |foo| +//@[09:010) | ├─Token(Assignment) |=| +//@[11:067) | └─ParameterizedTypeInstantiationSyntax +//@[11:019) | ├─IdentifierSyntax +//@[11:019) | | └─Token(Identifier) |resource| +//@[19:020) | ├─Token(LeftChevron) |<| +//@[20:066) | ├─ParameterizedTypeArgumentSyntax +//@[20:066) | | └─StringSyntax +//@[20:066) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[66:067) | └─Token(RightChevron) |>| +//@[67:069) ├─Token(NewLine) |\n\n| + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { +//@[00:160) ├─ParameterDeclarationSyntax +//@[00:005) | ├─Token(Identifier) |param| +//@[06:009) | ├─IdentifierSyntax +//@[06:009) | | └─Token(Identifier) |bar| +//@[10:057) | ├─ParameterizedTypeInstantiationSyntax +//@[10:018) | | ├─IdentifierSyntax +//@[10:018) | | | └─Token(Identifier) |resource| +//@[18:019) | | ├─Token(LeftChevron) |<| +//@[19:056) | | ├─ParameterizedTypeArgumentSyntax +//@[19:056) | | | └─StringSyntax +//@[19:056) | | | └─Token(StringComplete) |'Microsoft.Resources/tags@2022-09-01'| +//@[56:057) | | └─Token(RightChevron) |>| +//@[58:160) | └─ParameterDefaultValueSyntax +//@[58:059) | ├─Token(Assignment) |=| +//@[60:160) | └─ObjectSyntax +//@[60:061) | ├─Token(LeftBrace) |{| +//@[61:062) | ├─Token(NewLine) |\n| + name: 'default' +//@[02:017) | ├─ObjectPropertySyntax +//@[02:006) | | ├─IdentifierSyntax +//@[02:006) | | | └─Token(Identifier) |name| +//@[06:007) | | ├─Token(Colon) |:| +//@[08:017) | | └─StringSyntax +//@[08:017) | | └─Token(StringComplete) |'default'| +//@[17:018) | ├─Token(NewLine) |\n| + properties: { +//@[02:078) | ├─ObjectPropertySyntax +//@[02:012) | | ├─IdentifierSyntax +//@[02:012) | | | └─Token(Identifier) |properties| +//@[12:013) | | ├─Token(Colon) |:| +//@[14:078) | | └─ObjectSyntax +//@[14:015) | | ├─Token(LeftBrace) |{| +//@[15:016) | | ├─Token(NewLine) |\n| + tags: { +//@[04:058) | | ├─ObjectPropertySyntax +//@[04:008) | | | ├─IdentifierSyntax +//@[04:008) | | | | └─Token(Identifier) |tags| +//@[08:009) | | | ├─Token(Colon) |:| +//@[10:058) | | | └─ObjectSyntax +//@[10:011) | | | ├─Token(LeftBrace) |{| +//@[11:012) | | | ├─Token(NewLine) |\n| + fizz: 'buzz' +//@[06:018) | | | ├─ObjectPropertySyntax +//@[06:010) | | | | ├─IdentifierSyntax +//@[06:010) | | | | | └─Token(Identifier) |fizz| +//@[10:011) | | | | ├─Token(Colon) |:| +//@[12:018) | | | | └─StringSyntax +//@[12:018) | | | | └─Token(StringComplete) |'buzz'| +//@[18:019) | | | ├─Token(NewLine) |\n| + snap: 'crackle' +//@[06:021) | | | ├─ObjectPropertySyntax +//@[06:010) | | | | ├─IdentifierSyntax +//@[06:010) | | | | | └─Token(Identifier) |snap| +//@[10:011) | | | | ├─Token(Colon) |:| +//@[12:021) | | | | └─StringSyntax +//@[12:021) | | | | └─Token(StringComplete) |'crackle'| +//@[21:022) | | | ├─Token(NewLine) |\n| + } +//@[04:005) | | | └─Token(RightBrace) |}| +//@[05:006) | | ├─Token(NewLine) |\n| + } +//@[02:003) | | └─Token(RightBrace) |}| +//@[03:004) | ├─Token(NewLine) |\n| +} +//@[00:001) | └─Token(RightBrace) |}| +//@[01:003) ├─Token(NewLine) |\n\n| + +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { +//@[00:124) ├─OutputDeclarationSyntax +//@[00:006) | ├─Token(Identifier) |output| +//@[07:010) | ├─IdentifierSyntax +//@[07:010) | | └─Token(Identifier) |baz| +//@[11:082) | ├─ParameterizedTypeInstantiationSyntax +//@[11:019) | | ├─IdentifierSyntax +//@[11:019) | | | └─Token(Identifier) |resource| +//@[19:020) | | ├─Token(LeftChevron) |<| +//@[20:081) | | ├─ParameterizedTypeArgumentSyntax +//@[20:081) | | | └─StringSyntax +//@[20:081) | | | └─Token(StringComplete) |'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'| +//@[81:082) | | └─Token(RightChevron) |>| +//@[83:084) | ├─Token(Assignment) |=| +//@[85:124) | └─ObjectSyntax +//@[85:086) | ├─Token(LeftBrace) |{| +//@[86:087) | ├─Token(NewLine) |\n| + name: 'myId' +//@[02:014) | ├─ObjectPropertySyntax +//@[02:006) | | ├─IdentifierSyntax +//@[02:006) | | | └─Token(Identifier) |name| +//@[06:007) | | ├─Token(Colon) |:| +//@[08:014) | | └─StringSyntax +//@[08:014) | | └─Token(StringComplete) |'myId'| +//@[14:015) | ├─Token(NewLine) |\n| + location: 'eastus' +//@[02:020) | ├─ObjectPropertySyntax +//@[02:010) | | ├─IdentifierSyntax +//@[02:010) | | | └─Token(Identifier) |location| +//@[10:011) | | ├─Token(Colon) |:| +//@[12:020) | | └─StringSyntax +//@[12:020) | | └─Token(StringComplete) |'eastus'| +//@[20:021) | ├─Token(NewLine) |\n| +} +//@[00:001) | └─Token(RightBrace) |}| +//@[01:002) ├─Token(NewLine) |\n| + +//@[00:000) └─Token(EndOfFile) || diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep new file mode 100644 index 00000000000..df57aee950a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep @@ -0,0 +1,80 @@ +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[00:04) Identifier |type| +//@[05:08) Identifier |foo| +//@[09:10) Assignment |=| +//@[11:19) Identifier |resource| +//@[19:20) LeftChevron |<| +//@[20:66) StringComplete |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[66:67) RightChevron |>| +//@[67:69) NewLine |\n\n| + +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { +//@[00:05) Identifier |param| +//@[06:09) Identifier |bar| +//@[10:18) Identifier |resource| +//@[18:19) LeftChevron |<| +//@[19:56) StringComplete |'Microsoft.Resources/tags@2022-09-01'| +//@[56:57) RightChevron |>| +//@[58:59) Assignment |=| +//@[60:61) LeftBrace |{| +//@[61:62) NewLine |\n| + name: 'default' +//@[02:06) Identifier |name| +//@[06:07) Colon |:| +//@[08:17) StringComplete |'default'| +//@[17:18) NewLine |\n| + properties: { +//@[02:12) Identifier |properties| +//@[12:13) Colon |:| +//@[14:15) LeftBrace |{| +//@[15:16) NewLine |\n| + tags: { +//@[04:08) Identifier |tags| +//@[08:09) Colon |:| +//@[10:11) LeftBrace |{| +//@[11:12) NewLine |\n| + fizz: 'buzz' +//@[06:10) Identifier |fizz| +//@[10:11) Colon |:| +//@[12:18) StringComplete |'buzz'| +//@[18:19) NewLine |\n| + snap: 'crackle' +//@[06:10) Identifier |snap| +//@[10:11) Colon |:| +//@[12:21) StringComplete |'crackle'| +//@[21:22) NewLine |\n| + } +//@[04:05) RightBrace |}| +//@[05:06) NewLine |\n| + } +//@[02:03) RightBrace |}| +//@[03:04) NewLine |\n| +} +//@[00:01) RightBrace |}| +//@[01:03) NewLine |\n\n| + +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { +//@[00:06) Identifier |output| +//@[07:10) Identifier |baz| +//@[11:19) Identifier |resource| +//@[19:20) LeftChevron |<| +//@[20:81) StringComplete |'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'| +//@[81:82) RightChevron |>| +//@[83:84) Assignment |=| +//@[85:86) LeftBrace |{| +//@[86:87) NewLine |\n| + name: 'myId' +//@[02:06) Identifier |name| +//@[06:07) Colon |:| +//@[08:14) StringComplete |'myId'| +//@[14:15) NewLine |\n| + location: 'eastus' +//@[02:10) Identifier |location| +//@[10:11) Colon |:| +//@[12:20) StringComplete |'eastus'| +//@[20:21) NewLine |\n| +} +//@[00:01) RightBrace |}| +//@[01:02) NewLine |\n| + +//@[00:00) EndOfFile || From f8c36ae221b853b875b12d692f615eb66749f617 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Thu, 4 Jan 2024 17:49:56 -0500 Subject: [PATCH 15/20] Update SignatureHelpTests to expect signature help for parameterizedTypeSyntax --- src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs b/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs index a14c834ff33..16134791df7 100644 --- a/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs @@ -120,7 +120,7 @@ public async Task NonFunctionCallSyntaxShouldProvideNoSignatureHelp(DataSet data new List(), (accumulated, current) => { - if (current is not FunctionCallSyntaxBase) + if (current is not FunctionCallSyntaxBase && current is not ParameterizedTypeInstantiationSyntaxBase) { accumulated.Add(current); } @@ -130,7 +130,7 @@ public async Task NonFunctionCallSyntaxShouldProvideNoSignatureHelp(DataSet data accumulated => accumulated, // requesting signature help on non-function nodes that are placed inside function call nodes will produce signature help // since we don't want that, stop the visitor from visiting inner nodes when a function call is encountered - (accumulated, current) => current is not FunctionCallSyntaxBase); + (accumulated, current) => current is not FunctionCallSyntaxBase && current is not ParameterizedTypeInstantiationSyntaxBase); foreach (var nonFunction in nonFunctions) { From 59515492f46cda67672e97ed79ceedf9737ca984 Mon Sep 17 00:00:00 2001 From: Anthony Martin <38542602+anthony-c-martin@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:40:57 -0500 Subject: [PATCH 16/20] Add some baselines --- src/Bicep.Core.Samples/DataSets.cs | 2 + .../bicepconfig.json | 5 + .../InvalidResourceDerivedTypes_LF/main.bicep | 29 ++ .../main.diagnostics.bicep | 49 +++ .../main.formatted.bicep | 29 ++ .../main.pprint.bicep | 35 ++ .../main.symbols.bicep | 46 +++ .../main.syntax.bicep | 298 ++++++++++++++++++ .../main.tokens.bicep | 181 +++++++++++ .../ResourceDerivedTypes_LF/main.bicep | 24 ++ .../main.diagnostics.bicep | 24 ++ .../main.formatted.bicep | 24 ++ .../ResourceDerivedTypes_LF/main.ir.bicep | 47 ++- .../ResourceDerivedTypes_LF/main.json | 58 +++- .../ResourceDerivedTypes_LF/main.pprint.bicep | 22 ++ .../main.sourcemap.bicep | 80 +++++ .../main.symbolicnames.json | 58 +++- .../main.symbols.bicep | 27 ++ .../ResourceDerivedTypes_LF/main.syntax.bicep | 173 +++++++++- .../ResourceDerivedTypes_LF/main.tokens.bicep | 108 +++++++ 20 files changed, 1315 insertions(+), 4 deletions(-) create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/bicepconfig.json create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep create mode 100644 src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep diff --git a/src/Bicep.Core.Samples/DataSets.cs b/src/Bicep.Core.Samples/DataSets.cs index ab4f5b17755..d47ff6f2f74 100644 --- a/src/Bicep.Core.Samples/DataSets.cs +++ b/src/Bicep.Core.Samples/DataSets.cs @@ -40,6 +40,8 @@ public static class DataSets public static DataSet InvalidResources_CRLF => CreateDataSet(); + public static DataSet InvalidResourceDerivedTypes_LF => CreateDataSet(); + public static DataSet InvalidRuntimeValueUsages_LF => CreateDataSet(); public static DataSet ValidDeployTimeUsages_LF => CreateDataSet(); diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/bicepconfig.json b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/bicepconfig.json new file mode 100644 index 00000000000..f532743d2ac --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/bicepconfig.json @@ -0,0 +1,5 @@ +{ + "experimentalFeaturesEnabled": { + "resourceDerivedTypes": true + } +} diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep new file mode 100644 index 00000000000..53e2983ea89 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep @@ -0,0 +1,29 @@ +type invalid1 = resource + +type invalid2 = resource<> + +type invalid3 = resource<'abc', 'def'> +type invalid4 = resource +type invalid5 = resource<'Microsoft.Storage/storageAccounts'> +type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> +type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> +type invalid8 = resource<'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'> +type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> +type invalid10 = resource<'abc' 'def'> +type invalid11 = resource<123> +type invalid12 = resource + +type thisIsWeird = resource +> + +type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> + +@sealed() // this was offered as a completion +type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + +type hello = { + @discriminator('hi') + bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> +} diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep new file mode 100644 index 00000000000..cd03a161771 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep @@ -0,0 +1,49 @@ +type invalid1 = resource +//@[16:24) [BCP231 (Error)] Using resource-typed parameters and outputs requires enabling EXPERIMENTAL feature "ResourceTypedParamsAndOutputs". (CodeDescription: none) |resource| +//@[24:24) [BCP068 (Error)] Expected a resource type string. Specify a valid resource type of format "@". (CodeDescription: none) || + +type invalid2 = resource<> +//@[24:26) [BCP071 (Error)] Expected 1 argument, but got 0. (CodeDescription: none) |<>| + +type invalid3 = resource<'abc', 'def'> +//@[24:38) [BCP071 (Error)] Expected 1 argument, but got 2. (CodeDescription: none) |<'abc', 'def'>| +type invalid4 = resource +//@[25:30) [BCP070 (Error)] Argument of type "{ bar: Astronomer.Astro/organizations }" is not assignable to parameter of type "string". (CodeDescription: none) |hello| +type invalid5 = resource<'Microsoft.Storage/storageAccounts'> +//@[25:60) [BCP029 (Error)] The resource type is not valid. Specify a valid resource type of format "@". (CodeDescription: none) |'Microsoft.Storage/storageAccounts'| +type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> +//@[25:61) [BCP029 (Error)] The resource type is not valid. Specify a valid resource type of format "@". (CodeDescription: none) |'Microsoft.Storage/storageAccounts@'| +type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> +//@[25:66) [BCP029 (Error)] The resource type is not valid. Specify a valid resource type of format "@". (CodeDescription: none) |'Microsoft.Storage/storageAccounts@hello'| +type invalid8 = resource<'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'> +//@[25:89) [BCP208 (Error)] The specified namespace "notARealNamespace" is not recognized. Specify a resource reference using one of the following namespaces: "az", "sys". (CodeDescription: none) |'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'| +type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> +//@[25:72) [BCP029 (Error)] The resource type is not valid. Specify a valid resource type of format "@". (CodeDescription: none) |':Microsoft.Storage/storageAccounts@2022-09-01'| +type invalid10 = resource<'abc' 'def'> +//@[25:38) [BCP071 (Error)] Expected 1 argument, but got 2. (CodeDescription: none) |<'abc' 'def'>| +//@[32:32) [BCP236 (Error)] Expected a new line or comma character at this location. (CodeDescription: none) || +type invalid11 = resource<123> +//@[26:29) [BCP070 (Error)] Argument of type "123" is not assignable to parameter of type "string". (CodeDescription: none) |123| +type invalid12 = resource +//@[25:42) [BCP071 (Error)] Expected 1 argument, but got 2. (CodeDescription: none) || +//@[39:39) [BCP236 (Error)] Expected a new line or comma character at this location. (CodeDescription: none) || +//@[40:40) [BCP243 (Error)] Parentheses must contain exactly one expression. (CodeDescription: none) || + +type thisIsWeird = resource +> + +type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> + +@sealed() // this was offered as a completion +//@[00:09) [BCP316 (Error)] The "sealed" decorator may not be used on object types with an explicit additional properties type declaration. (CodeDescription: none) |@sealed()| +type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + +type hello = { + @discriminator('hi') +//@[02:22) [BCP363 (Error)] The "discriminator" decorator can only be applied to object-only union types with unique member types. (CodeDescription: none) |@discriminator('hi')| + bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> +} + diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep new file mode 100644 index 00000000000..669f5360a0d --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep @@ -0,0 +1,29 @@ +type invalid1 = resource + +type invalid2 = resource < > + +type invalid3 = resource < 'abc' , 'def' > +type invalid4 = resource < hello > +type invalid5 = resource < 'Microsoft.Storage/storageAccounts' > +type invalid6 = resource < 'Microsoft.Storage/storageAccounts@' > +type invalid7 = resource < 'Microsoft.Storage/storageAccounts@hello' > +type invalid8 = resource < 'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01' > +type invalid9 = resource < ':Microsoft.Storage/storageAccounts@2022-09-01' > +type invalid10 = resource<'abc' 'def'> +type invalid11 = resource < 123 > +type invalid12 = resource + +type thisIsWeird = resource +> + +type shouldWeBlockThis = resource < 'Microsoft.${'Storage'}/storageAccounts@2022-09-01' > + +@sealed() // this was offered as a completion +type shouldWeBlockThis2 = resource < 'Microsoft.Storage/storageAccounts@2022-09-01' > + +type hello = { + @discriminator('hi') + bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> +} diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep new file mode 100644 index 00000000000..b8ba5e654b8 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep @@ -0,0 +1,35 @@ +type invalid1 = resource + +type invalid2 = resource<> + +type invalid3 = resource<'abc', 'def'> +type invalid4 = resource +type invalid5 = resource<'Microsoft.Storage/storageAccounts'> +type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> +type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> +type invalid8 = resource< + 'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01' +> +type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> +type invalid10 = resource<'abc' 'def'> +type invalid11 = resource<123> +type invalid12 = resource + +type thisIsWeird = resource +> + +type shouldWeBlockThis = resource< + 'Microsoft.${'Storage'}/storageAccounts@2022-09-01' +> + +@sealed() // this was offered as a completion +type shouldWeBlockThis2 = resource< + 'Microsoft.Storage/storageAccounts@2022-09-01' +> + +type hello = { + @discriminator('hi') + bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> +} diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep new file mode 100644 index 00000000000..84cd3ca06b6 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep @@ -0,0 +1,46 @@ +type invalid1 = resource +//@[5:13) TypeAlias invalid1. Type: error. Declaration start char: 0, length: 24 + +type invalid2 = resource<> +//@[5:13) TypeAlias invalid2. Type: error. Declaration start char: 0, length: 26 + +type invalid3 = resource<'abc', 'def'> +//@[5:13) TypeAlias invalid3. Type: error. Declaration start char: 0, length: 38 +type invalid4 = resource +//@[5:13) TypeAlias invalid4. Type: error. Declaration start char: 0, length: 31 +type invalid5 = resource<'Microsoft.Storage/storageAccounts'> +//@[5:13) TypeAlias invalid5. Type: error. Declaration start char: 0, length: 61 +type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> +//@[5:13) TypeAlias invalid6. Type: error. Declaration start char: 0, length: 62 +type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> +//@[5:13) TypeAlias invalid7. Type: error. Declaration start char: 0, length: 67 +type invalid8 = resource<'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'> +//@[5:13) TypeAlias invalid8. Type: error. Declaration start char: 0, length: 90 +type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> +//@[5:13) TypeAlias invalid9. Type: error. Declaration start char: 0, length: 73 +type invalid10 = resource<'abc' 'def'> +//@[5:14) TypeAlias invalid10. Type: error. Declaration start char: 0, length: 38 +type invalid11 = resource<123> +//@[5:14) TypeAlias invalid11. Type: error. Declaration start char: 0, length: 30 +type invalid12 = resource +//@[5:14) TypeAlias invalid12. Type: error. Declaration start char: 0, length: 42 + +type thisIsWeird = resource. Declaration start char: 0, length: 94 +*/'Astronomer.Astro/organizations@2023-08-01-preview' +/// > +> + +type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +//@[5:22) TypeAlias shouldWeBlockThis. Type: Type. Declaration start char: 0, length: 86 + +@sealed() // this was offered as a completion +type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[5:23) TypeAlias shouldWeBlockThis2. Type: error. Declaration start char: 0, length: 128 + +type hello = { +//@[5:10) TypeAlias hello. Type: Type<{ bar: Astronomer.Astro/organizations }>. Declaration start char: 0, length: 108 + @discriminator('hi') + bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> +} + diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep new file mode 100644 index 00000000000..6b006ea50b1 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep @@ -0,0 +1,298 @@ +type invalid1 = resource +//@[00:1020) ProgramSyntax +//@[00:0024) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid1| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0024) | └─ResourceTypeSyntax +//@[16:0024) | ├─Token(Identifier) |resource| +//@[24:0024) | └─SkippedTriviaSyntax +//@[24:0026) ├─Token(NewLine) |\n\n| + +type invalid2 = resource<> +//@[00:0026) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid2| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0026) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0026) | └─Token(RightChevron) |>| +//@[26:0028) ├─Token(NewLine) |\n\n| + +type invalid3 = resource<'abc', 'def'> +//@[00:0038) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid3| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0038) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0030) | ├─ParameterizedTypeArgumentSyntax +//@[25:0030) | | └─StringSyntax +//@[25:0030) | | └─Token(StringComplete) |'abc'| +//@[30:0031) | ├─Token(Comma) |,| +//@[32:0037) | ├─ParameterizedTypeArgumentSyntax +//@[32:0037) | | └─StringSyntax +//@[32:0037) | | └─Token(StringComplete) |'def'| +//@[37:0038) | └─Token(RightChevron) |>| +//@[38:0039) ├─Token(NewLine) |\n| +type invalid4 = resource +//@[00:0031) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid4| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0031) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0030) | ├─ParameterizedTypeArgumentSyntax +//@[25:0030) | | └─VariableAccessSyntax +//@[25:0030) | | └─IdentifierSyntax +//@[25:0030) | | └─Token(Identifier) |hello| +//@[30:0031) | └─Token(RightChevron) |>| +//@[31:0032) ├─Token(NewLine) |\n| +type invalid5 = resource<'Microsoft.Storage/storageAccounts'> +//@[00:0061) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid5| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0061) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0060) | ├─ParameterizedTypeArgumentSyntax +//@[25:0060) | | └─StringSyntax +//@[25:0060) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts'| +//@[60:0061) | └─Token(RightChevron) |>| +//@[61:0062) ├─Token(NewLine) |\n| +type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> +//@[00:0062) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid6| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0062) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0061) | ├─ParameterizedTypeArgumentSyntax +//@[25:0061) | | └─StringSyntax +//@[25:0061) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@'| +//@[61:0062) | └─Token(RightChevron) |>| +//@[62:0063) ├─Token(NewLine) |\n| +type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> +//@[00:0067) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid7| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0067) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0066) | ├─ParameterizedTypeArgumentSyntax +//@[25:0066) | | └─StringSyntax +//@[25:0066) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@hello'| +//@[66:0067) | └─Token(RightChevron) |>| +//@[67:0068) ├─Token(NewLine) |\n| +type invalid8 = resource<'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:0090) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid8| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0090) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0089) | ├─ParameterizedTypeArgumentSyntax +//@[25:0089) | | └─StringSyntax +//@[25:0089) | | └─Token(StringComplete) |'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'| +//@[89:0090) | └─Token(RightChevron) |>| +//@[90:0091) ├─Token(NewLine) |\n| +type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:0073) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0013) | ├─IdentifierSyntax +//@[05:0013) | | └─Token(Identifier) |invalid9| +//@[14:0015) | ├─Token(Assignment) |=| +//@[16:0073) | └─ParameterizedTypeInstantiationSyntax +//@[16:0024) | ├─IdentifierSyntax +//@[16:0024) | | └─Token(Identifier) |resource| +//@[24:0025) | ├─Token(LeftChevron) |<| +//@[25:0072) | ├─ParameterizedTypeArgumentSyntax +//@[25:0072) | | └─StringSyntax +//@[25:0072) | | └─Token(StringComplete) |':Microsoft.Storage/storageAccounts@2022-09-01'| +//@[72:0073) | └─Token(RightChevron) |>| +//@[73:0074) ├─Token(NewLine) |\n| +type invalid10 = resource<'abc' 'def'> +//@[00:0038) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0014) | ├─IdentifierSyntax +//@[05:0014) | | └─Token(Identifier) |invalid10| +//@[15:0016) | ├─Token(Assignment) |=| +//@[17:0038) | └─ParameterizedTypeInstantiationSyntax +//@[17:0025) | ├─IdentifierSyntax +//@[17:0025) | | └─Token(Identifier) |resource| +//@[25:0026) | ├─Token(LeftChevron) |<| +//@[26:0031) | ├─ParameterizedTypeArgumentSyntax +//@[26:0031) | | └─StringSyntax +//@[26:0031) | | └─Token(StringComplete) |'abc'| +//@[32:0032) | ├─SkippedTriviaSyntax +//@[32:0037) | ├─ParameterizedTypeArgumentSyntax +//@[32:0037) | | └─StringSyntax +//@[32:0037) | | └─Token(StringComplete) |'def'| +//@[37:0038) | └─Token(RightChevron) |>| +//@[38:0039) ├─Token(NewLine) |\n| +type invalid11 = resource<123> +//@[00:0030) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0014) | ├─IdentifierSyntax +//@[05:0014) | | └─Token(Identifier) |invalid11| +//@[15:0016) | ├─Token(Assignment) |=| +//@[17:0030) | └─ParameterizedTypeInstantiationSyntax +//@[17:0025) | ├─IdentifierSyntax +//@[17:0025) | | └─Token(Identifier) |resource| +//@[25:0026) | ├─Token(LeftChevron) |<| +//@[26:0029) | ├─ParameterizedTypeArgumentSyntax +//@[26:0029) | | └─IntegerLiteralSyntax +//@[26:0029) | | └─Token(Integer) |123| +//@[29:0030) | └─Token(RightChevron) |>| +//@[30:0031) ├─Token(NewLine) |\n| +type invalid12 = resource +//@[00:0042) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0014) | ├─IdentifierSyntax +//@[05:0014) | | └─Token(Identifier) |invalid12| +//@[15:0016) | ├─Token(Assignment) |=| +//@[17:0042) | └─ParameterizedTypeInstantiationSyntax +//@[17:0025) | ├─IdentifierSyntax +//@[17:0025) | | └─Token(Identifier) |resource| +//@[25:0026) | ├─Token(LeftChevron) |<| +//@[26:0039) | ├─ParameterizedTypeArgumentSyntax +//@[26:0039) | | └─VariableAccessSyntax +//@[26:0039) | | └─IdentifierSyntax +//@[26:0039) | | └─Token(Identifier) |resourceGroup| +//@[39:0039) | ├─SkippedTriviaSyntax +//@[39:0041) | ├─ParameterizedTypeArgumentSyntax +//@[39:0041) | | └─ParenthesizedExpressionSyntax +//@[39:0040) | | ├─Token(LeftParen) |(| +//@[40:0040) | | ├─SkippedTriviaSyntax +//@[40:0041) | | └─Token(RightParen) |)| +//@[41:0042) | └─Token(RightChevron) |>| +//@[42:0044) ├─Token(NewLine) |\n\n| + +type thisIsWeird = resource +//@[06:0007) | ├─Token(NewLine) |\n| +> +//@[00:0001) | └─Token(RightChevron) |>| +//@[01:0003) ├─Token(NewLine) |\n\n| + +type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +//@[00:0086) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0022) | ├─IdentifierSyntax +//@[05:0022) | | └─Token(Identifier) |shouldWeBlockThis| +//@[23:0024) | ├─Token(Assignment) |=| +//@[25:0086) | └─ParameterizedTypeInstantiationSyntax +//@[25:0033) | ├─IdentifierSyntax +//@[25:0033) | | └─Token(Identifier) |resource| +//@[33:0034) | ├─Token(LeftChevron) |<| +//@[34:0085) | ├─ParameterizedTypeArgumentSyntax +//@[34:0085) | | └─StringSyntax +//@[34:0047) | | ├─Token(StringLeftPiece) |'Microsoft.${| +//@[47:0056) | | ├─StringSyntax +//@[47:0056) | | | └─Token(StringComplete) |'Storage'| +//@[56:0085) | | └─Token(StringRightPiece) |}/storageAccounts@2022-09-01'| +//@[85:0086) | └─Token(RightChevron) |>| +//@[86:0088) ├─Token(NewLine) |\n\n| + +@sealed() // this was offered as a completion +//@[00:0128) ├─TypeDeclarationSyntax +//@[00:0009) | ├─DecoratorSyntax +//@[00:0001) | | ├─Token(At) |@| +//@[01:0009) | | └─FunctionCallSyntax +//@[01:0007) | | ├─IdentifierSyntax +//@[01:0007) | | | └─Token(Identifier) |sealed| +//@[07:0008) | | ├─Token(LeftParen) |(| +//@[08:0009) | | └─Token(RightParen) |)| +//@[45:0046) | ├─Token(NewLine) |\n| +type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0023) | ├─IdentifierSyntax +//@[05:0023) | | └─Token(Identifier) |shouldWeBlockThis2| +//@[24:0025) | ├─Token(Assignment) |=| +//@[26:0082) | └─ParameterizedTypeInstantiationSyntax +//@[26:0034) | ├─IdentifierSyntax +//@[26:0034) | | └─Token(Identifier) |resource| +//@[34:0035) | ├─Token(LeftChevron) |<| +//@[35:0081) | ├─ParameterizedTypeArgumentSyntax +//@[35:0081) | | └─StringSyntax +//@[35:0081) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2022-09-01'| +//@[81:0082) | └─Token(RightChevron) |>| +//@[82:0084) ├─Token(NewLine) |\n\n| + +type hello = { +//@[00:0108) ├─TypeDeclarationSyntax +//@[00:0004) | ├─Token(Identifier) |type| +//@[05:0010) | ├─IdentifierSyntax +//@[05:0010) | | └─Token(Identifier) |hello| +//@[11:0012) | ├─Token(Assignment) |=| +//@[13:0108) | └─ObjectTypeSyntax +//@[13:0014) | ├─Token(LeftBrace) |{| +//@[14:0015) | ├─Token(NewLine) |\n| + @discriminator('hi') +//@[02:0091) | ├─ObjectTypePropertySyntax +//@[02:0022) | | ├─DecoratorSyntax +//@[02:0003) | | | ├─Token(At) |@| +//@[03:0022) | | | └─FunctionCallSyntax +//@[03:0016) | | | ├─IdentifierSyntax +//@[03:0016) | | | | └─Token(Identifier) |discriminator| +//@[16:0017) | | | ├─Token(LeftParen) |(| +//@[17:0021) | | | ├─FunctionArgumentSyntax +//@[17:0021) | | | | └─StringSyntax +//@[17:0021) | | | | └─Token(StringComplete) |'hi'| +//@[21:0022) | | | └─Token(RightParen) |)| +//@[22:0023) | | ├─Token(NewLine) |\n| + bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> +//@[02:0005) | | ├─IdentifierSyntax +//@[02:0005) | | | └─Token(Identifier) |bar| +//@[05:0006) | | ├─Token(Colon) |:| +//@[07:0068) | | └─ParameterizedTypeInstantiationSyntax +//@[07:0015) | | ├─IdentifierSyntax +//@[07:0015) | | | └─Token(Identifier) |resource| +//@[15:0016) | | ├─Token(LeftChevron) |<| +//@[16:0067) | | ├─ParameterizedTypeArgumentSyntax +//@[16:0067) | | | └─StringSyntax +//@[16:0067) | | | └─Token(StringComplete) |'Astronomer.Astro/organizations@2023-08-01-preview'| +//@[67:0068) | | └─Token(RightChevron) |>| +//@[68:0069) | ├─Token(NewLine) |\n| +} +//@[00:0001) | └─Token(RightBrace) |}| +//@[01:0002) ├─Token(NewLine) |\n| + +//@[00:0000) └─Token(EndOfFile) || diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep new file mode 100644 index 00000000000..6b8e2225687 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep @@ -0,0 +1,181 @@ +type invalid1 = resource +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid1| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:26) NewLine |\n\n| + +type invalid2 = resource<> +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid2| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:26) RightChevron |>| +//@[26:28) NewLine |\n\n| + +type invalid3 = resource<'abc', 'def'> +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid3| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:30) StringComplete |'abc'| +//@[30:31) Comma |,| +//@[32:37) StringComplete |'def'| +//@[37:38) RightChevron |>| +//@[38:39) NewLine |\n| +type invalid4 = resource +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid4| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:30) Identifier |hello| +//@[30:31) RightChevron |>| +//@[31:32) NewLine |\n| +type invalid5 = resource<'Microsoft.Storage/storageAccounts'> +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid5| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:60) StringComplete |'Microsoft.Storage/storageAccounts'| +//@[60:61) RightChevron |>| +//@[61:62) NewLine |\n| +type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid6| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:61) StringComplete |'Microsoft.Storage/storageAccounts@'| +//@[61:62) RightChevron |>| +//@[62:63) NewLine |\n| +type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid7| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:66) StringComplete |'Microsoft.Storage/storageAccounts@hello'| +//@[66:67) RightChevron |>| +//@[67:68) NewLine |\n| +type invalid8 = resource<'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid8| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:89) StringComplete |'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'| +//@[89:90) RightChevron |>| +//@[90:91) NewLine |\n| +type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:04) Identifier |type| +//@[05:13) Identifier |invalid9| +//@[14:15) Assignment |=| +//@[16:24) Identifier |resource| +//@[24:25) LeftChevron |<| +//@[25:72) StringComplete |':Microsoft.Storage/storageAccounts@2022-09-01'| +//@[72:73) RightChevron |>| +//@[73:74) NewLine |\n| +type invalid10 = resource<'abc' 'def'> +//@[00:04) Identifier |type| +//@[05:14) Identifier |invalid10| +//@[15:16) Assignment |=| +//@[17:25) Identifier |resource| +//@[25:26) LeftChevron |<| +//@[26:31) StringComplete |'abc'| +//@[32:37) StringComplete |'def'| +//@[37:38) RightChevron |>| +//@[38:39) NewLine |\n| +type invalid11 = resource<123> +//@[00:04) Identifier |type| +//@[05:14) Identifier |invalid11| +//@[15:16) Assignment |=| +//@[17:25) Identifier |resource| +//@[25:26) LeftChevron |<| +//@[26:29) Integer |123| +//@[29:30) RightChevron |>| +//@[30:31) NewLine |\n| +type invalid12 = resource +//@[00:04) Identifier |type| +//@[05:14) Identifier |invalid12| +//@[15:16) Assignment |=| +//@[17:25) Identifier |resource| +//@[25:26) LeftChevron |<| +//@[26:39) Identifier |resourceGroup| +//@[39:40) LeftParen |(| +//@[40:41) RightParen |)| +//@[41:42) RightChevron |>| +//@[42:44) NewLine |\n\n| + +type thisIsWeird = resource +//@[06:07) NewLine |\n| +> +//@[00:01) RightChevron |>| +//@[01:03) NewLine |\n\n| + +type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +//@[00:04) Identifier |type| +//@[05:22) Identifier |shouldWeBlockThis| +//@[23:24) Assignment |=| +//@[25:33) Identifier |resource| +//@[33:34) LeftChevron |<| +//@[34:47) StringLeftPiece |'Microsoft.${| +//@[47:56) StringComplete |'Storage'| +//@[56:85) StringRightPiece |}/storageAccounts@2022-09-01'| +//@[85:86) RightChevron |>| +//@[86:88) NewLine |\n\n| + +@sealed() // this was offered as a completion +//@[00:01) At |@| +//@[01:07) Identifier |sealed| +//@[07:08) LeftParen |(| +//@[08:09) RightParen |)| +//@[45:46) NewLine |\n| +type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:04) Identifier |type| +//@[05:23) Identifier |shouldWeBlockThis2| +//@[24:25) Assignment |=| +//@[26:34) Identifier |resource| +//@[34:35) LeftChevron |<| +//@[35:81) StringComplete |'Microsoft.Storage/storageAccounts@2022-09-01'| +//@[81:82) RightChevron |>| +//@[82:84) NewLine |\n\n| + +type hello = { +//@[00:04) Identifier |type| +//@[05:10) Identifier |hello| +//@[11:12) Assignment |=| +//@[13:14) LeftBrace |{| +//@[14:15) NewLine |\n| + @discriminator('hi') +//@[02:03) At |@| +//@[03:16) Identifier |discriminator| +//@[16:17) LeftParen |(| +//@[17:21) StringComplete |'hi'| +//@[21:22) RightParen |)| +//@[22:23) NewLine |\n| + bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> +//@[02:05) Identifier |bar| +//@[05:06) Colon |:| +//@[07:15) Identifier |resource| +//@[15:16) LeftChevron |<| +//@[16:67) StringComplete |'Astronomer.Astro/organizations@2023-08-01-preview'| +//@[67:68) RightChevron |>| +//@[68:69) NewLine |\n| +} +//@[00:01) RightBrace |}| +//@[01:02) NewLine |\n| + +//@[00:00) EndOfFile || diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep index bd6d59223ad..7bec575e3f2 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.bicep @@ -1,5 +1,29 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +type test = { + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + resC: sys.array + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +} + +type strangeFormattings = { + test: resource< + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + test2: resource <'Microsoft.Storage/storageAccounts@2023-01-01'> + test3: resource +} + +@description('I love space(s)') +type test2 = resource< + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { name: 'default' properties: { diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep index 5745cee9feb..72d67c43e2d 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.diagnostics.bicep @@ -1,5 +1,29 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +type test = { + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + resC: sys.array + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +} + +type strangeFormattings = { + test: resource< + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + test2: resource <'Microsoft.Storage/storageAccounts@2023-01-01'> + test3: resource +} + +@description('I love space(s)') +type test2 = resource< + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { //@[6:9) [no-unused-params (Warning)] Parameter "bar" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-params)) |bar| name: 'default' diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep index d1b2ff13b3b..cf7338411ed 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep @@ -1,5 +1,29 @@ type foo = resource < 'Microsoft.Storage/storageAccounts@2023-01-01' > +type test = { + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + resC: sys.array + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +} + +type strangeFormattings = { + test: resource< + + 'Astronomer.Astro/organizations@2023-08-01-preview' + + > + test2: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + test3: resource +} + +@description('I love space(s)') +type test2 = resource < + + 'Astronomer.Astro/organizations@2023-08-01-preview' + + > + param bar resource < 'Microsoft.Resources/tags@2022-09-01' > = { name: 'default' properties: { diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep index ecd5aa0d9d6..2b688783781 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.ir.bicep @@ -1,8 +1,53 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> -//@[00:356) ProgramExpression +//@[00:974) ProgramExpression //@[00:067) ├─DeclaredTypeExpression { Name = foo } //@[11:067) | └─ResourceDerivedTypeExpression { Name = Microsoft.Storage/storageAccounts } +type test = { +//@[00:239) ├─DeclaredTypeExpression { Name = test } +//@[12:239) | └─ObjectTypeExpression { Name = { resA: Microsoft.Storage/storageAccounts, resB: Microsoft.Storage/storageAccounts, resC: array, resD: Microsoft.Storage/storageAccounts } } + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[02:064) | ├─ObjectTypePropertyExpression +//@[08:064) | | └─ResourceDerivedTypeExpression { Name = Microsoft.Storage/storageAccounts } + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[02:068) | ├─ObjectTypePropertyExpression +//@[08:068) | | └─ResourceDerivedTypeExpression { Name = Microsoft.Storage/storageAccounts } + resC: sys.array +//@[02:017) | ├─ObjectTypePropertyExpression +//@[08:017) | | └─FullyQualifiedAmbientTypeReferenceExpression { Name = sys.array } + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +//@[02:071) | └─ObjectTypePropertyExpression +//@[08:071) | └─ResourceDerivedTypeExpression { Name = Microsoft.Storage/storageAccounts } +} + +type strangeFormattings = { +//@[00:258) ├─DeclaredTypeExpression { Name = strangeFormattings } +//@[26:258) | └─ObjectTypeExpression { Name = { test: Astronomer.Astro/organizations, test2: Microsoft.Storage/storageAccounts, test3: Microsoft.Storage/storageAccounts } } + test: resource< +//@[02:075) | ├─ObjectTypePropertyExpression +//@[08:075) | | └─ResourceDerivedTypeExpression { Name = Astronomer.Astro/organizations } + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + test2: resource <'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[02:069) | ├─ObjectTypePropertyExpression +//@[09:069) | | └─ResourceDerivedTypeExpression { Name = Microsoft.Storage/storageAccounts } + test3: resource +//@[02:082) | └─ObjectTypePropertyExpression +//@[09:082) | └─ResourceDerivedTypeExpression { Name = Microsoft.Storage/storageAccounts } +} + +@description('I love space(s)') +//@[00:115) ├─DeclaredTypeExpression { Name = test2 } +//@[13:030) | ├─StringLiteralExpression { Value = I love space(s) } +type test2 = resource< +//@[13:083) | └─ResourceDerivedTypeExpression { Name = Astronomer.Astro/organizations } + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { //@[00:160) ├─DeclaredParameterExpression { Name = bar } //@[10:057) | ├─ResourceDerivedTypeExpression { Name = Microsoft.Resources/tags } diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json index 10aeb05b1b1..23be197482a 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "12442568148038291906" + "templateHash": "2406575907779073888" } }, "definitions": { @@ -15,6 +15,62 @@ "metadata": { "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" } + }, + "test": { + "type": "object", + "properties": { + "resA": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + }, + "resB": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2022-09-01" + } + }, + "resC": { + "type": "array" + }, + "resD": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + }, + "strangeFormattings": { + "type": "object", + "properties": { + "test": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Astronomer.Astro/organizations@2023-08-01-preview" + } + }, + "test2": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + }, + "test3": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + } + } + }, + "test2": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Astronomer.Astro/organizations@2023-08-01-preview", + "description": "I love space(s)" + } } }, "parameters": { diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep index 4f35e469cfe..3bc0d388c5b 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.pprint.bicep @@ -1,5 +1,27 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +type test = { + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + resC: sys.array + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +} + +type strangeFormattings = { + test: resource< + 'Astronomer.Astro/organizations@2023-08-01-preview' + > + test2: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + test3: resource< + /* */ 'Microsoft.Storage/storageAccounts@2023-01-01' /* */ + > +} + +@description('I love space(s)') +type test2 = resource< + 'Astronomer.Astro/organizations@2023-08-01-preview' +> + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { name: 'default' properties: { diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep index 1b9ed06445e..47b07f6c45c 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.sourcemap.bicep @@ -4,8 +4,88 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> //@ "metadata": { //@ "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" //@ } +//@ }, + +type test = { +//@ "test": { +//@ "type": "object", +//@ "properties": { +//@ "resA": { +//@ }, +//@ "resB": { +//@ }, +//@ "resD": { +//@ } +//@ } +//@ }, + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" +//@ } + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2022-09-01" +//@ } + resC: sys.array +//@ "resC": { +//@ "type": "array" +//@ }, + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2022-09-01" +//@ } +} + +type strangeFormattings = { +//@ "strangeFormattings": { +//@ "type": "object", +//@ "properties": { +//@ "test": { +//@ }, +//@ "test2": { +//@ }, +//@ "test3": { +//@ } +//@ } +//@ }, + test: resource< +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Astronomer.Astro/organizations@2023-08-01-preview" +//@ } + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + test2: resource <'Microsoft.Storage/storageAccounts@2023-01-01'> +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" +//@ } + test3: resource +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" +//@ } +} + +@description('I love space(s)') +//@ "description": "I love space(s)" +type test2 = resource< +//@ "test2": { +//@ "type": "object", +//@ "metadata": { +//@ "__bicep_resource_derived_type!": "Astronomer.Astro/organizations@2023-08-01-preview", +//@ } //@ } + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { //@ "bar": { //@ "type": "object", diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json index 10aeb05b1b1..23be197482a 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbolicnames.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "12442568148038291906" + "templateHash": "2406575907779073888" } }, "definitions": { @@ -15,6 +15,62 @@ "metadata": { "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" } + }, + "test": { + "type": "object", + "properties": { + "resA": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + }, + "resB": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2022-09-01" + } + }, + "resC": { + "type": "array" + }, + "resD": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2022-09-01" + } + } + } + }, + "strangeFormattings": { + "type": "object", + "properties": { + "test": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Astronomer.Astro/organizations@2023-08-01-preview" + } + }, + "test2": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + }, + "test3": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2023-01-01" + } + } + } + }, + "test2": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": "Astronomer.Astro/organizations@2023-08-01-preview", + "description": "I love space(s)" + } } }, "parameters": { diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep index 350f761c3f9..184a44be2da 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.symbols.bicep @@ -1,6 +1,33 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> //@[5:08) TypeAlias foo. Type: Type. Declaration start char: 0, length: 67 +type test = { +//@[5:09) TypeAlias test. Type: Type<{ resA: Microsoft.Storage/storageAccounts, resB: Microsoft.Storage/storageAccounts, resC: array, resD: Microsoft.Storage/storageAccounts }>. Declaration start char: 0, length: 239 + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> + resC: sys.array + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +} + +type strangeFormattings = { +//@[5:23) TypeAlias strangeFormattings. Type: Type<{ test: Astronomer.Astro/organizations, test2: Microsoft.Storage/storageAccounts, test3: Microsoft.Storage/storageAccounts }>. Declaration start char: 0, length: 258 + test: resource< + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + test2: resource <'Microsoft.Storage/storageAccounts@2023-01-01'> + test3: resource +} + +@description('I love space(s)') +type test2 = resource< +//@[5:10) TypeAlias test2. Type: Type. Declaration start char: 0, length: 115 + + 'Astronomer.Astro/organizations@2023-08-01-preview' + +> + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { //@[6:09) Parameter bar. Type: Microsoft.Resources/tags. Declaration start char: 0, length: 160 name: 'default' diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep index f05f87c4c41..6426b8aca06 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.syntax.bicep @@ -1,5 +1,5 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> -//@[00:356) ProgramSyntax +//@[00:974) ProgramSyntax //@[00:067) ├─TypeDeclarationSyntax //@[00:004) | ├─Token(Identifier) |type| //@[05:008) | ├─IdentifierSyntax @@ -15,6 +15,177 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> //@[66:067) | └─Token(RightChevron) |>| //@[67:069) ├─Token(NewLine) |\n\n| +type test = { +//@[00:239) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:009) | ├─IdentifierSyntax +//@[05:009) | | └─Token(Identifier) |test| +//@[10:011) | ├─Token(Assignment) |=| +//@[12:239) | └─ObjectTypeSyntax +//@[12:013) | ├─Token(LeftBrace) |{| +//@[13:014) | ├─Token(NewLine) |\n| + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[02:064) | ├─ObjectTypePropertySyntax +//@[02:006) | | ├─IdentifierSyntax +//@[02:006) | | | └─Token(Identifier) |resA| +//@[06:007) | | ├─Token(Colon) |:| +//@[08:064) | | └─ParameterizedTypeInstantiationSyntax +//@[08:016) | | ├─IdentifierSyntax +//@[08:016) | | | └─Token(Identifier) |resource| +//@[16:017) | | ├─Token(LeftChevron) |<| +//@[17:063) | | ├─ParameterizedTypeArgumentSyntax +//@[17:063) | | | └─StringSyntax +//@[17:063) | | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[63:064) | | └─Token(RightChevron) |>| +//@[64:065) | ├─Token(NewLine) |\n| + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[02:068) | ├─ObjectTypePropertySyntax +//@[02:006) | | ├─IdentifierSyntax +//@[02:006) | | | └─Token(Identifier) |resB| +//@[06:007) | | ├─Token(Colon) |:| +//@[08:068) | | └─InstanceParameterizedTypeInstantiationSyntax +//@[08:011) | | ├─VariableAccessSyntax +//@[08:011) | | | └─IdentifierSyntax +//@[08:011) | | | └─Token(Identifier) |sys| +//@[11:012) | | ├─Token(Dot) |.| +//@[12:020) | | ├─IdentifierSyntax +//@[12:020) | | | └─Token(Identifier) |resource| +//@[20:021) | | ├─Token(LeftChevron) |<| +//@[21:067) | | ├─ParameterizedTypeArgumentSyntax +//@[21:067) | | | └─StringSyntax +//@[21:067) | | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2022-09-01'| +//@[67:068) | | └─Token(RightChevron) |>| +//@[68:069) | ├─Token(NewLine) |\n| + resC: sys.array +//@[02:017) | ├─ObjectTypePropertySyntax +//@[02:006) | | ├─IdentifierSyntax +//@[02:006) | | | └─Token(Identifier) |resC| +//@[06:007) | | ├─Token(Colon) |:| +//@[08:017) | | └─PropertyAccessSyntax +//@[08:011) | | ├─VariableAccessSyntax +//@[08:011) | | | └─IdentifierSyntax +//@[08:011) | | | └─Token(Identifier) |sys| +//@[11:012) | | ├─Token(Dot) |.| +//@[12:017) | | └─IdentifierSyntax +//@[12:017) | | └─Token(Identifier) |array| +//@[17:018) | ├─Token(NewLine) |\n| + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +//@[02:071) | ├─ObjectTypePropertySyntax +//@[02:006) | | ├─IdentifierSyntax +//@[02:006) | | | └─Token(Identifier) |resD| +//@[06:007) | | ├─Token(Colon) |:| +//@[08:071) | | └─InstanceParameterizedTypeInstantiationSyntax +//@[08:011) | | ├─VariableAccessSyntax +//@[08:011) | | | └─IdentifierSyntax +//@[08:011) | | | └─Token(Identifier) |sys| +//@[11:012) | | ├─Token(Dot) |.| +//@[12:020) | | ├─IdentifierSyntax +//@[12:020) | | | └─Token(Identifier) |resource| +//@[20:021) | | ├─Token(LeftChevron) |<| +//@[21:070) | | ├─ParameterizedTypeArgumentSyntax +//@[21:070) | | | └─StringSyntax +//@[21:070) | | | └─Token(StringComplete) |'az:Microsoft.Storage/storageAccounts@2022-09-01'| +//@[70:071) | | └─Token(RightChevron) |>| +//@[71:072) | ├─Token(NewLine) |\n| +} +//@[00:001) | └─Token(RightBrace) |}| +//@[01:003) ├─Token(NewLine) |\n\n| + +type strangeFormattings = { +//@[00:258) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:023) | ├─IdentifierSyntax +//@[05:023) | | └─Token(Identifier) |strangeFormattings| +//@[24:025) | ├─Token(Assignment) |=| +//@[26:258) | └─ObjectTypeSyntax +//@[26:027) | ├─Token(LeftBrace) |{| +//@[27:028) | ├─Token(NewLine) |\n| + test: resource< +//@[02:075) | ├─ObjectTypePropertySyntax +//@[02:006) | | ├─IdentifierSyntax +//@[02:006) | | | └─Token(Identifier) |test| +//@[06:007) | | ├─Token(Colon) |:| +//@[08:075) | | └─ParameterizedTypeInstantiationSyntax +//@[08:016) | | ├─IdentifierSyntax +//@[08:016) | | | └─Token(Identifier) |resource| +//@[16:017) | | ├─Token(LeftChevron) |<| +//@[17:019) | | ├─Token(NewLine) |\n\n| + + 'Astronomer.Astro/organizations@2023-08-01-preview' +//@[02:053) | | ├─ParameterizedTypeArgumentSyntax +//@[02:053) | | | └─StringSyntax +//@[02:053) | | | └─Token(StringComplete) |'Astronomer.Astro/organizations@2023-08-01-preview'| +//@[53:055) | | ├─Token(NewLine) |\n\n| + +> +//@[00:001) | | └─Token(RightChevron) |>| +//@[01:002) | ├─Token(NewLine) |\n| + test2: resource <'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[02:069) | ├─ObjectTypePropertySyntax +//@[02:007) | | ├─IdentifierSyntax +//@[02:007) | | | └─Token(Identifier) |test2| +//@[07:008) | | ├─Token(Colon) |:| +//@[09:069) | | └─ParameterizedTypeInstantiationSyntax +//@[09:017) | | ├─IdentifierSyntax +//@[09:017) | | | └─Token(Identifier) |resource| +//@[21:022) | | ├─Token(LeftChevron) |<| +//@[22:068) | | ├─ParameterizedTypeArgumentSyntax +//@[22:068) | | | └─StringSyntax +//@[22:068) | | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[68:069) | | └─Token(RightChevron) |>| +//@[69:070) | ├─Token(NewLine) |\n| + test3: resource +//@[02:082) | ├─ObjectTypePropertySyntax +//@[02:007) | | ├─IdentifierSyntax +//@[02:007) | | | └─Token(Identifier) |test3| +//@[07:008) | | ├─Token(Colon) |:| +//@[09:082) | | └─ParameterizedTypeInstantiationSyntax +//@[09:017) | | ├─IdentifierSyntax +//@[09:017) | | | └─Token(Identifier) |resource| +//@[17:018) | | ├─Token(LeftChevron) |<| +//@[26:072) | | ├─ParameterizedTypeArgumentSyntax +//@[26:072) | | | └─StringSyntax +//@[26:072) | | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[81:082) | | └─Token(RightChevron) |>| +//@[82:083) | ├─Token(NewLine) |\n| +} +//@[00:001) | └─Token(RightBrace) |}| +//@[01:003) ├─Token(NewLine) |\n\n| + +@description('I love space(s)') +//@[00:115) ├─TypeDeclarationSyntax +//@[00:031) | ├─DecoratorSyntax +//@[00:001) | | ├─Token(At) |@| +//@[01:031) | | └─FunctionCallSyntax +//@[01:012) | | ├─IdentifierSyntax +//@[01:012) | | | └─Token(Identifier) |description| +//@[12:013) | | ├─Token(LeftParen) |(| +//@[13:030) | | ├─FunctionArgumentSyntax +//@[13:030) | | | └─StringSyntax +//@[13:030) | | | └─Token(StringComplete) |'I love space(s)'| +//@[30:031) | | └─Token(RightParen) |)| +//@[31:032) | ├─Token(NewLine) |\n| +type test2 = resource< +//@[00:004) | ├─Token(Identifier) |type| +//@[05:010) | ├─IdentifierSyntax +//@[05:010) | | └─Token(Identifier) |test2| +//@[11:012) | ├─Token(Assignment) |=| +//@[13:083) | └─ParameterizedTypeInstantiationSyntax +//@[13:021) | ├─IdentifierSyntax +//@[13:021) | | └─Token(Identifier) |resource| +//@[21:022) | ├─Token(LeftChevron) |<| +//@[22:024) | ├─Token(NewLine) |\n\n| + + 'Astronomer.Astro/organizations@2023-08-01-preview' +//@[05:056) | ├─ParameterizedTypeArgumentSyntax +//@[05:056) | | └─StringSyntax +//@[05:056) | | └─Token(StringComplete) |'Astronomer.Astro/organizations@2023-08-01-preview'| +//@[56:058) | ├─Token(NewLine) |\n\n| + +> +//@[00:001) | └─Token(RightChevron) |>| +//@[01:003) ├─Token(NewLine) |\n\n| + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { //@[00:160) ├─ParameterDeclarationSyntax //@[00:005) | ├─Token(Identifier) |param| diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep index df57aee950a..aa179abca1f 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.tokens.bicep @@ -8,6 +8,114 @@ type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> //@[66:67) RightChevron |>| //@[67:69) NewLine |\n\n| +type test = { +//@[00:04) Identifier |type| +//@[05:09) Identifier |test| +//@[10:11) Assignment |=| +//@[12:13) LeftBrace |{| +//@[13:14) NewLine |\n| + resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[02:06) Identifier |resA| +//@[06:07) Colon |:| +//@[08:16) Identifier |resource| +//@[16:17) LeftChevron |<| +//@[17:63) StringComplete |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[63:64) RightChevron |>| +//@[64:65) NewLine |\n| + resB: sys.resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[02:06) Identifier |resB| +//@[06:07) Colon |:| +//@[08:11) Identifier |sys| +//@[11:12) Dot |.| +//@[12:20) Identifier |resource| +//@[20:21) LeftChevron |<| +//@[21:67) StringComplete |'Microsoft.Storage/storageAccounts@2022-09-01'| +//@[67:68) RightChevron |>| +//@[68:69) NewLine |\n| + resC: sys.array +//@[02:06) Identifier |resC| +//@[06:07) Colon |:| +//@[08:11) Identifier |sys| +//@[11:12) Dot |.| +//@[12:17) Identifier |array| +//@[17:18) NewLine |\n| + resD: sys.resource<'az:Microsoft.Storage/storageAccounts@2022-09-01'> +//@[02:06) Identifier |resD| +//@[06:07) Colon |:| +//@[08:11) Identifier |sys| +//@[11:12) Dot |.| +//@[12:20) Identifier |resource| +//@[20:21) LeftChevron |<| +//@[21:70) StringComplete |'az:Microsoft.Storage/storageAccounts@2022-09-01'| +//@[70:71) RightChevron |>| +//@[71:72) NewLine |\n| +} +//@[00:01) RightBrace |}| +//@[01:03) NewLine |\n\n| + +type strangeFormattings = { +//@[00:04) Identifier |type| +//@[05:23) Identifier |strangeFormattings| +//@[24:25) Assignment |=| +//@[26:27) LeftBrace |{| +//@[27:28) NewLine |\n| + test: resource< +//@[02:06) Identifier |test| +//@[06:07) Colon |:| +//@[08:16) Identifier |resource| +//@[16:17) LeftChevron |<| +//@[17:19) NewLine |\n\n| + + 'Astronomer.Astro/organizations@2023-08-01-preview' +//@[02:53) StringComplete |'Astronomer.Astro/organizations@2023-08-01-preview'| +//@[53:55) NewLine |\n\n| + +> +//@[00:01) RightChevron |>| +//@[01:02) NewLine |\n| + test2: resource <'Microsoft.Storage/storageAccounts@2023-01-01'> +//@[02:07) Identifier |test2| +//@[07:08) Colon |:| +//@[09:17) Identifier |resource| +//@[21:22) LeftChevron |<| +//@[22:68) StringComplete |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[68:69) RightChevron |>| +//@[69:70) NewLine |\n| + test3: resource +//@[02:07) Identifier |test3| +//@[07:08) Colon |:| +//@[09:17) Identifier |resource| +//@[17:18) LeftChevron |<| +//@[26:72) StringComplete |'Microsoft.Storage/storageAccounts@2023-01-01'| +//@[81:82) RightChevron |>| +//@[82:83) NewLine |\n| +} +//@[00:01) RightBrace |}| +//@[01:03) NewLine |\n\n| + +@description('I love space(s)') +//@[00:01) At |@| +//@[01:12) Identifier |description| +//@[12:13) LeftParen |(| +//@[13:30) StringComplete |'I love space(s)'| +//@[30:31) RightParen |)| +//@[31:32) NewLine |\n| +type test2 = resource< +//@[00:04) Identifier |type| +//@[05:10) Identifier |test2| +//@[11:12) Assignment |=| +//@[13:21) Identifier |resource| +//@[21:22) LeftChevron |<| +//@[22:24) NewLine |\n\n| + + 'Astronomer.Astro/organizations@2023-08-01-preview' +//@[05:56) StringComplete |'Astronomer.Astro/organizations@2023-08-01-preview'| +//@[56:58) NewLine |\n\n| + +> +//@[00:01) RightChevron |>| +//@[01:03) NewLine |\n\n| + param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { //@[00:05) Identifier |param| //@[06:09) Identifier |bar| From 80c1dd6576d0ee30814a6392828c692a737ae016 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 5 Jan 2024 14:10:30 -0500 Subject: [PATCH 17/20] Fix V1 formatting issue --- .../main.formatted.bicep | 22 +++++++++---------- .../main.formatted.bicep | 14 ++++++------ .../PrettyPrint/DocumentBuildVisitor.cs | 20 +++++++++++++++++ 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep index 669f5360a0d..53e2983ea89 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep @@ -1,16 +1,16 @@ type invalid1 = resource -type invalid2 = resource < > +type invalid2 = resource<> -type invalid3 = resource < 'abc' , 'def' > -type invalid4 = resource < hello > -type invalid5 = resource < 'Microsoft.Storage/storageAccounts' > -type invalid6 = resource < 'Microsoft.Storage/storageAccounts@' > -type invalid7 = resource < 'Microsoft.Storage/storageAccounts@hello' > -type invalid8 = resource < 'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01' > -type invalid9 = resource < ':Microsoft.Storage/storageAccounts@2022-09-01' > +type invalid3 = resource<'abc', 'def'> +type invalid4 = resource +type invalid5 = resource<'Microsoft.Storage/storageAccounts'> +type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> +type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> +type invalid8 = resource<'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'> +type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> type invalid10 = resource<'abc' 'def'> -type invalid11 = resource < 123 > +type invalid11 = resource<123> type invalid12 = resource type thisIsWeird = resource > -type shouldWeBlockThis = resource < 'Microsoft.${'Storage'}/storageAccounts@2022-09-01' > +type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> @sealed() // this was offered as a completion -type shouldWeBlockThis2 = resource < 'Microsoft.Storage/storageAccounts@2022-09-01' > +type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> type hello = { @discriminator('hi') diff --git a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep index cf7338411ed..d67ee982035 100644 --- a/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/ResourceDerivedTypes_LF/main.formatted.bicep @@ -1,4 +1,4 @@ -type foo = resource < 'Microsoft.Storage/storageAccounts@2023-01-01' > +type foo = resource<'Microsoft.Storage/storageAccounts@2023-01-01'> type test = { resA: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> @@ -10,7 +10,7 @@ type test = { type strangeFormattings = { test: resource< - 'Astronomer.Astro/organizations@2023-08-01-preview' + 'Astronomer.Astro/organizations@2023-08-01-preview' > test2: resource<'Microsoft.Storage/storageAccounts@2023-01-01'> @@ -18,13 +18,13 @@ type strangeFormattings = { } @description('I love space(s)') -type test2 = resource < +type test2 = resource< - 'Astronomer.Astro/organizations@2023-08-01-preview' + 'Astronomer.Astro/organizations@2023-08-01-preview' - > +> -param bar resource < 'Microsoft.Resources/tags@2022-09-01' > = { +param bar resource<'Microsoft.Resources/tags@2022-09-01'> = { name: 'default' properties: { tags: { @@ -34,7 +34,7 @@ param bar resource < 'Microsoft.Resources/tags@2022-09-01' > = { } } -output baz resource < 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' > = { +output baz resource<'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31'> = { name: 'myId' location: 'eastus' } diff --git a/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs b/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs index b43a4705b18..e64a4d58f67 100644 --- a/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs +++ b/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs @@ -759,6 +759,26 @@ public override void VisitCompileTimeImportFromClauseSyntax(CompileTimeImportFro this.Visit(syntax.Path); }); + public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) => + this.BuildWithConcat(() => + { + this.Visit(syntax.Name); + this.Visit(syntax.OpenChevron); + this.VisitCommaAndNewLineSeparated(syntax.Children, leadingAndTrailingSpace: false); + this.Visit(syntax.CloseChevron); + }); + + public override void VisitInstanceParameterizedTypeInstantiationSyntax(InstanceParameterizedTypeInstantiationSyntax syntax) => + this.BuildWithConcat(() => + { + this.Visit(syntax.BaseExpression); + this.Visit(syntax.Dot); + this.Visit(syntax.Name); + this.Visit(syntax.OpenChevron); + this.VisitCommaAndNewLineSeparated(syntax.Children, leadingAndTrailingSpace: false); + this.Visit(syntax.CloseChevron); + }); + private static ILinkedDocument Text(string text) => CommonTextCache.TryGetValue(text, out var cached) ? cached : new TextDocument(text); From 96a0954d2ff40a86d5a2c1a7a1ea5bf6fe6b67fe Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 5 Jan 2024 14:22:50 -0500 Subject: [PATCH 18/20] Fix failing tests --- src/Bicep.Core.UnitTests/Utils/TestSyntaxHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Bicep.Core.UnitTests/Utils/TestSyntaxHelper.cs b/src/Bicep.Core.UnitTests/Utils/TestSyntaxHelper.cs index 630d94bfce0..f3f3e07ebd1 100644 --- a/src/Bicep.Core.UnitTests/Utils/TestSyntaxHelper.cs +++ b/src/Bicep.Core.UnitTests/Utils/TestSyntaxHelper.cs @@ -11,6 +11,7 @@ public static class TestSyntaxHelper public static bool NodeShouldBeBound(ISymbolReference symbolReference) => symbolReference is not InstanceFunctionCallSyntax and not PropertyAccessSyntax - and not ObjectPropertySyntax; + and not ObjectPropertySyntax + and not InstanceParameterizedTypeInstantiationSyntax; } } From 32db8422937adc4bc47912e5f0301ebe221f3edb Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 5 Jan 2024 15:48:08 -0500 Subject: [PATCH 19/20] Clean up diagnostic reporting on `sealed` misuses --- .../InvalidResourceDerivedTypes_LF/main.bicep | 8 +- .../main.diagnostics.bicep | 13 +- .../main.formatted.bicep | 8 +- .../main.pprint.bicep | 8 +- .../main.symbols.bicep | 14 +- .../main.syntax.bicep | 544 +++++++++--------- .../main.tokens.bicep | 48 +- .../main.symbols.bicep | 2 +- .../Diagnostics/DiagnosticBuilder.cs | 5 + src/Bicep.Core/LanguageConstants.cs | 1 + .../Namespaces/SystemNamespaceType.cs | 27 +- .../TypeSystem/DeclaredTypeManager.cs | 6 +- 12 files changed, 353 insertions(+), 331 deletions(-) diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep index 53e2983ea89..039f583dd90 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.bicep @@ -14,14 +14,14 @@ type invalid11 = resource<123> type invalid12 = resource type thisIsWeird = resource > -type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +type interpolated = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> -@sealed() // this was offered as a completion -type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +@sealed() +type shouldNotBeSealable = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> type hello = { @discriminator('hi') diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep index cd03a161771..015f0bcd1ee 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.diagnostics.bicep @@ -30,16 +30,17 @@ type invalid12 = resource //@[40:40) [BCP243 (Error)] Parentheses must contain exactly one expression. (CodeDescription: none) || type thisIsWeird = resource > -type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +type interpolated = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +//@[29:80) [BCP032 (Error)] The value must be a compile-time constant. (CodeDescription: none) |'Microsoft.${'Storage'}/storageAccounts@2022-09-01'| -@sealed() // this was offered as a completion -//@[00:09) [BCP316 (Error)] The "sealed" decorator may not be used on object types with an explicit additional properties type declaration. (CodeDescription: none) |@sealed()| -type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +@sealed() +//@[00:09) [BCP386 (Error)] The decorator "sealed" may not be used on statements whose declared type is a reference to a resource-derived type. (CodeDescription: none) |@sealed()| +type shouldNotBeSealable = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> type hello = { @discriminator('hi') diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep index 53e2983ea89..039f583dd90 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.formatted.bicep @@ -14,14 +14,14 @@ type invalid11 = resource<123> type invalid12 = resource type thisIsWeird = resource > -type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +type interpolated = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> -@sealed() // this was offered as a completion -type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +@sealed() +type shouldNotBeSealable = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> type hello = { @discriminator('hi') diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep index b8ba5e654b8..05b6206b34b 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.pprint.bicep @@ -16,16 +16,16 @@ type invalid11 = resource<123> type invalid12 = resource type thisIsWeird = resource > -type shouldWeBlockThis = resource< +type interpolated = resource< 'Microsoft.${'Storage'}/storageAccounts@2022-09-01' > -@sealed() // this was offered as a completion -type shouldWeBlockThis2 = resource< +@sealed() +type shouldNotBeSealable = resource< 'Microsoft.Storage/storageAccounts@2022-09-01' > diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep index 84cd3ca06b6..000178d8ea4 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.symbols.bicep @@ -26,17 +26,17 @@ type invalid12 = resource //@[5:14) TypeAlias invalid12. Type: error. Declaration start char: 0, length: 42 type thisIsWeird = resource. Declaration start char: 0, length: 94 -*/'Astronomer.Astro/organizations@2023-08-01-preview' +//@[5:16) TypeAlias thisIsWeird. Type: Type. Declaration start char: 0, length: 93 +*/'Astronomer.Astro/organizations@2023-08-01-preview' /// > > -type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> -//@[5:22) TypeAlias shouldWeBlockThis. Type: Type. Declaration start char: 0, length: 86 +type interpolated = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +//@[5:17) TypeAlias interpolated. Type: error. Declaration start char: 0, length: 81 -@sealed() // this was offered as a completion -type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> -//@[5:23) TypeAlias shouldWeBlockThis2. Type: error. Declaration start char: 0, length: 128 +@sealed() +type shouldNotBeSealable = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[5:24) TypeAlias shouldNotBeSealable. Type: Type. Declaration start char: 0, length: 93 type hello = { //@[5:10) TypeAlias hello. Type: Type<{ bar: Astronomer.Astro/organizations }>. Declaration start char: 0, length: 108 diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep index 6b006ea50b1..2a46b383d4e 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.syntax.bicep @@ -1,298 +1,298 @@ type invalid1 = resource -//@[00:1020) ProgramSyntax -//@[00:0024) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid1| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0024) | └─ResourceTypeSyntax -//@[16:0024) | ├─Token(Identifier) |resource| -//@[24:0024) | └─SkippedTriviaSyntax -//@[24:0026) ├─Token(NewLine) |\n\n| +//@[00:979) ProgramSyntax +//@[00:024) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid1| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:024) | └─ResourceTypeSyntax +//@[16:024) | ├─Token(Identifier) |resource| +//@[24:024) | └─SkippedTriviaSyntax +//@[24:026) ├─Token(NewLine) |\n\n| type invalid2 = resource<> -//@[00:0026) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid2| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0026) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0026) | └─Token(RightChevron) |>| -//@[26:0028) ├─Token(NewLine) |\n\n| +//@[00:026) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid2| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:026) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:026) | └─Token(RightChevron) |>| +//@[26:028) ├─Token(NewLine) |\n\n| type invalid3 = resource<'abc', 'def'> -//@[00:0038) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid3| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0038) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0030) | ├─ParameterizedTypeArgumentSyntax -//@[25:0030) | | └─StringSyntax -//@[25:0030) | | └─Token(StringComplete) |'abc'| -//@[30:0031) | ├─Token(Comma) |,| -//@[32:0037) | ├─ParameterizedTypeArgumentSyntax -//@[32:0037) | | └─StringSyntax -//@[32:0037) | | └─Token(StringComplete) |'def'| -//@[37:0038) | └─Token(RightChevron) |>| -//@[38:0039) ├─Token(NewLine) |\n| +//@[00:038) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid3| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:038) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:030) | ├─ParameterizedTypeArgumentSyntax +//@[25:030) | | └─StringSyntax +//@[25:030) | | └─Token(StringComplete) |'abc'| +//@[30:031) | ├─Token(Comma) |,| +//@[32:037) | ├─ParameterizedTypeArgumentSyntax +//@[32:037) | | └─StringSyntax +//@[32:037) | | └─Token(StringComplete) |'def'| +//@[37:038) | └─Token(RightChevron) |>| +//@[38:039) ├─Token(NewLine) |\n| type invalid4 = resource -//@[00:0031) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid4| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0031) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0030) | ├─ParameterizedTypeArgumentSyntax -//@[25:0030) | | └─VariableAccessSyntax -//@[25:0030) | | └─IdentifierSyntax -//@[25:0030) | | └─Token(Identifier) |hello| -//@[30:0031) | └─Token(RightChevron) |>| -//@[31:0032) ├─Token(NewLine) |\n| +//@[00:031) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid4| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:031) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:030) | ├─ParameterizedTypeArgumentSyntax +//@[25:030) | | └─VariableAccessSyntax +//@[25:030) | | └─IdentifierSyntax +//@[25:030) | | └─Token(Identifier) |hello| +//@[30:031) | └─Token(RightChevron) |>| +//@[31:032) ├─Token(NewLine) |\n| type invalid5 = resource<'Microsoft.Storage/storageAccounts'> -//@[00:0061) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid5| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0061) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0060) | ├─ParameterizedTypeArgumentSyntax -//@[25:0060) | | └─StringSyntax -//@[25:0060) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts'| -//@[60:0061) | └─Token(RightChevron) |>| -//@[61:0062) ├─Token(NewLine) |\n| +//@[00:061) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid5| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:061) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:060) | ├─ParameterizedTypeArgumentSyntax +//@[25:060) | | └─StringSyntax +//@[25:060) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts'| +//@[60:061) | └─Token(RightChevron) |>| +//@[61:062) ├─Token(NewLine) |\n| type invalid6 = resource<'Microsoft.Storage/storageAccounts@'> -//@[00:0062) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid6| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0062) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0061) | ├─ParameterizedTypeArgumentSyntax -//@[25:0061) | | └─StringSyntax -//@[25:0061) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@'| -//@[61:0062) | └─Token(RightChevron) |>| -//@[62:0063) ├─Token(NewLine) |\n| +//@[00:062) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid6| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:062) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:061) | ├─ParameterizedTypeArgumentSyntax +//@[25:061) | | └─StringSyntax +//@[25:061) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@'| +//@[61:062) | └─Token(RightChevron) |>| +//@[62:063) ├─Token(NewLine) |\n| type invalid7 = resource<'Microsoft.Storage/storageAccounts@hello'> -//@[00:0067) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid7| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0067) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0066) | ├─ParameterizedTypeArgumentSyntax -//@[25:0066) | | └─StringSyntax -//@[25:0066) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@hello'| -//@[66:0067) | └─Token(RightChevron) |>| -//@[67:0068) ├─Token(NewLine) |\n| +//@[00:067) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid7| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:067) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:066) | ├─ParameterizedTypeArgumentSyntax +//@[25:066) | | └─StringSyntax +//@[25:066) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@hello'| +//@[66:067) | └─Token(RightChevron) |>| +//@[67:068) ├─Token(NewLine) |\n| type invalid8 = resource<'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'> -//@[00:0090) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid8| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0090) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0089) | ├─ParameterizedTypeArgumentSyntax -//@[25:0089) | | └─StringSyntax -//@[25:0089) | | └─Token(StringComplete) |'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'| -//@[89:0090) | └─Token(RightChevron) |>| -//@[90:0091) ├─Token(NewLine) |\n| +//@[00:090) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid8| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:090) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:089) | ├─ParameterizedTypeArgumentSyntax +//@[25:089) | | └─StringSyntax +//@[25:089) | | └─Token(StringComplete) |'notARealNamespace:Microsoft.Storage/storageAccounts@2022-09-01'| +//@[89:090) | └─Token(RightChevron) |>| +//@[90:091) ├─Token(NewLine) |\n| type invalid9 = resource<':Microsoft.Storage/storageAccounts@2022-09-01'> -//@[00:0073) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0013) | ├─IdentifierSyntax -//@[05:0013) | | └─Token(Identifier) |invalid9| -//@[14:0015) | ├─Token(Assignment) |=| -//@[16:0073) | └─ParameterizedTypeInstantiationSyntax -//@[16:0024) | ├─IdentifierSyntax -//@[16:0024) | | └─Token(Identifier) |resource| -//@[24:0025) | ├─Token(LeftChevron) |<| -//@[25:0072) | ├─ParameterizedTypeArgumentSyntax -//@[25:0072) | | └─StringSyntax -//@[25:0072) | | └─Token(StringComplete) |':Microsoft.Storage/storageAccounts@2022-09-01'| -//@[72:0073) | └─Token(RightChevron) |>| -//@[73:0074) ├─Token(NewLine) |\n| +//@[00:073) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:013) | ├─IdentifierSyntax +//@[05:013) | | └─Token(Identifier) |invalid9| +//@[14:015) | ├─Token(Assignment) |=| +//@[16:073) | └─ParameterizedTypeInstantiationSyntax +//@[16:024) | ├─IdentifierSyntax +//@[16:024) | | └─Token(Identifier) |resource| +//@[24:025) | ├─Token(LeftChevron) |<| +//@[25:072) | ├─ParameterizedTypeArgumentSyntax +//@[25:072) | | └─StringSyntax +//@[25:072) | | └─Token(StringComplete) |':Microsoft.Storage/storageAccounts@2022-09-01'| +//@[72:073) | └─Token(RightChevron) |>| +//@[73:074) ├─Token(NewLine) |\n| type invalid10 = resource<'abc' 'def'> -//@[00:0038) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0014) | ├─IdentifierSyntax -//@[05:0014) | | └─Token(Identifier) |invalid10| -//@[15:0016) | ├─Token(Assignment) |=| -//@[17:0038) | └─ParameterizedTypeInstantiationSyntax -//@[17:0025) | ├─IdentifierSyntax -//@[17:0025) | | └─Token(Identifier) |resource| -//@[25:0026) | ├─Token(LeftChevron) |<| -//@[26:0031) | ├─ParameterizedTypeArgumentSyntax -//@[26:0031) | | └─StringSyntax -//@[26:0031) | | └─Token(StringComplete) |'abc'| -//@[32:0032) | ├─SkippedTriviaSyntax -//@[32:0037) | ├─ParameterizedTypeArgumentSyntax -//@[32:0037) | | └─StringSyntax -//@[32:0037) | | └─Token(StringComplete) |'def'| -//@[37:0038) | └─Token(RightChevron) |>| -//@[38:0039) ├─Token(NewLine) |\n| +//@[00:038) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:014) | ├─IdentifierSyntax +//@[05:014) | | └─Token(Identifier) |invalid10| +//@[15:016) | ├─Token(Assignment) |=| +//@[17:038) | └─ParameterizedTypeInstantiationSyntax +//@[17:025) | ├─IdentifierSyntax +//@[17:025) | | └─Token(Identifier) |resource| +//@[25:026) | ├─Token(LeftChevron) |<| +//@[26:031) | ├─ParameterizedTypeArgumentSyntax +//@[26:031) | | └─StringSyntax +//@[26:031) | | └─Token(StringComplete) |'abc'| +//@[32:032) | ├─SkippedTriviaSyntax +//@[32:037) | ├─ParameterizedTypeArgumentSyntax +//@[32:037) | | └─StringSyntax +//@[32:037) | | └─Token(StringComplete) |'def'| +//@[37:038) | └─Token(RightChevron) |>| +//@[38:039) ├─Token(NewLine) |\n| type invalid11 = resource<123> -//@[00:0030) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0014) | ├─IdentifierSyntax -//@[05:0014) | | └─Token(Identifier) |invalid11| -//@[15:0016) | ├─Token(Assignment) |=| -//@[17:0030) | └─ParameterizedTypeInstantiationSyntax -//@[17:0025) | ├─IdentifierSyntax -//@[17:0025) | | └─Token(Identifier) |resource| -//@[25:0026) | ├─Token(LeftChevron) |<| -//@[26:0029) | ├─ParameterizedTypeArgumentSyntax -//@[26:0029) | | └─IntegerLiteralSyntax -//@[26:0029) | | └─Token(Integer) |123| -//@[29:0030) | └─Token(RightChevron) |>| -//@[30:0031) ├─Token(NewLine) |\n| +//@[00:030) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:014) | ├─IdentifierSyntax +//@[05:014) | | └─Token(Identifier) |invalid11| +//@[15:016) | ├─Token(Assignment) |=| +//@[17:030) | └─ParameterizedTypeInstantiationSyntax +//@[17:025) | ├─IdentifierSyntax +//@[17:025) | | └─Token(Identifier) |resource| +//@[25:026) | ├─Token(LeftChevron) |<| +//@[26:029) | ├─ParameterizedTypeArgumentSyntax +//@[26:029) | | └─IntegerLiteralSyntax +//@[26:029) | | └─Token(Integer) |123| +//@[29:030) | └─Token(RightChevron) |>| +//@[30:031) ├─Token(NewLine) |\n| type invalid12 = resource -//@[00:0042) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0014) | ├─IdentifierSyntax -//@[05:0014) | | └─Token(Identifier) |invalid12| -//@[15:0016) | ├─Token(Assignment) |=| -//@[17:0042) | └─ParameterizedTypeInstantiationSyntax -//@[17:0025) | ├─IdentifierSyntax -//@[17:0025) | | └─Token(Identifier) |resource| -//@[25:0026) | ├─Token(LeftChevron) |<| -//@[26:0039) | ├─ParameterizedTypeArgumentSyntax -//@[26:0039) | | └─VariableAccessSyntax -//@[26:0039) | | └─IdentifierSyntax -//@[26:0039) | | └─Token(Identifier) |resourceGroup| -//@[39:0039) | ├─SkippedTriviaSyntax -//@[39:0041) | ├─ParameterizedTypeArgumentSyntax -//@[39:0041) | | └─ParenthesizedExpressionSyntax -//@[39:0040) | | ├─Token(LeftParen) |(| -//@[40:0040) | | ├─SkippedTriviaSyntax -//@[40:0041) | | └─Token(RightParen) |)| -//@[41:0042) | └─Token(RightChevron) |>| -//@[42:0044) ├─Token(NewLine) |\n\n| +//@[00:042) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:014) | ├─IdentifierSyntax +//@[05:014) | | └─Token(Identifier) |invalid12| +//@[15:016) | ├─Token(Assignment) |=| +//@[17:042) | └─ParameterizedTypeInstantiationSyntax +//@[17:025) | ├─IdentifierSyntax +//@[17:025) | | └─Token(Identifier) |resource| +//@[25:026) | ├─Token(LeftChevron) |<| +//@[26:039) | ├─ParameterizedTypeArgumentSyntax +//@[26:039) | | └─VariableAccessSyntax +//@[26:039) | | └─IdentifierSyntax +//@[26:039) | | └─Token(Identifier) |resourceGroup| +//@[39:039) | ├─SkippedTriviaSyntax +//@[39:041) | ├─ParameterizedTypeArgumentSyntax +//@[39:041) | | └─ParenthesizedExpressionSyntax +//@[39:040) | | ├─Token(LeftParen) |(| +//@[40:040) | | ├─SkippedTriviaSyntax +//@[40:041) | | └─Token(RightParen) |)| +//@[41:042) | └─Token(RightChevron) |>| +//@[42:044) ├─Token(NewLine) |\n\n| type thisIsWeird = resource -//@[06:0007) | ├─Token(NewLine) |\n| +//@[06:007) | ├─Token(NewLine) |\n| > -//@[00:0001) | └─Token(RightChevron) |>| -//@[01:0003) ├─Token(NewLine) |\n\n| +//@[00:001) | └─Token(RightChevron) |>| +//@[01:003) ├─Token(NewLine) |\n\n| -type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> -//@[00:0086) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0022) | ├─IdentifierSyntax -//@[05:0022) | | └─Token(Identifier) |shouldWeBlockThis| -//@[23:0024) | ├─Token(Assignment) |=| -//@[25:0086) | └─ParameterizedTypeInstantiationSyntax -//@[25:0033) | ├─IdentifierSyntax -//@[25:0033) | | └─Token(Identifier) |resource| -//@[33:0034) | ├─Token(LeftChevron) |<| -//@[34:0085) | ├─ParameterizedTypeArgumentSyntax -//@[34:0085) | | └─StringSyntax -//@[34:0047) | | ├─Token(StringLeftPiece) |'Microsoft.${| -//@[47:0056) | | ├─StringSyntax -//@[47:0056) | | | └─Token(StringComplete) |'Storage'| -//@[56:0085) | | └─Token(StringRightPiece) |}/storageAccounts@2022-09-01'| -//@[85:0086) | └─Token(RightChevron) |>| -//@[86:0088) ├─Token(NewLine) |\n\n| +type interpolated = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +//@[00:081) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:017) | ├─IdentifierSyntax +//@[05:017) | | └─Token(Identifier) |interpolated| +//@[18:019) | ├─Token(Assignment) |=| +//@[20:081) | └─ParameterizedTypeInstantiationSyntax +//@[20:028) | ├─IdentifierSyntax +//@[20:028) | | └─Token(Identifier) |resource| +//@[28:029) | ├─Token(LeftChevron) |<| +//@[29:080) | ├─ParameterizedTypeArgumentSyntax +//@[29:080) | | └─StringSyntax +//@[29:042) | | ├─Token(StringLeftPiece) |'Microsoft.${| +//@[42:051) | | ├─StringSyntax +//@[42:051) | | | └─Token(StringComplete) |'Storage'| +//@[51:080) | | └─Token(StringRightPiece) |}/storageAccounts@2022-09-01'| +//@[80:081) | └─Token(RightChevron) |>| +//@[81:083) ├─Token(NewLine) |\n\n| -@sealed() // this was offered as a completion -//@[00:0128) ├─TypeDeclarationSyntax -//@[00:0009) | ├─DecoratorSyntax -//@[00:0001) | | ├─Token(At) |@| -//@[01:0009) | | └─FunctionCallSyntax -//@[01:0007) | | ├─IdentifierSyntax -//@[01:0007) | | | └─Token(Identifier) |sealed| -//@[07:0008) | | ├─Token(LeftParen) |(| -//@[08:0009) | | └─Token(RightParen) |)| -//@[45:0046) | ├─Token(NewLine) |\n| -type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0023) | ├─IdentifierSyntax -//@[05:0023) | | └─Token(Identifier) |shouldWeBlockThis2| -//@[24:0025) | ├─Token(Assignment) |=| -//@[26:0082) | └─ParameterizedTypeInstantiationSyntax -//@[26:0034) | ├─IdentifierSyntax -//@[26:0034) | | └─Token(Identifier) |resource| -//@[34:0035) | ├─Token(LeftChevron) |<| -//@[35:0081) | ├─ParameterizedTypeArgumentSyntax -//@[35:0081) | | └─StringSyntax -//@[35:0081) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2022-09-01'| -//@[81:0082) | └─Token(RightChevron) |>| -//@[82:0084) ├─Token(NewLine) |\n\n| +@sealed() +//@[00:093) ├─TypeDeclarationSyntax +//@[00:009) | ├─DecoratorSyntax +//@[00:001) | | ├─Token(At) |@| +//@[01:009) | | └─FunctionCallSyntax +//@[01:007) | | ├─IdentifierSyntax +//@[01:007) | | | └─Token(Identifier) |sealed| +//@[07:008) | | ├─Token(LeftParen) |(| +//@[08:009) | | └─Token(RightParen) |)| +//@[09:010) | ├─Token(NewLine) |\n| +type shouldNotBeSealable = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:004) | ├─Token(Identifier) |type| +//@[05:024) | ├─IdentifierSyntax +//@[05:024) | | └─Token(Identifier) |shouldNotBeSealable| +//@[25:026) | ├─Token(Assignment) |=| +//@[27:083) | └─ParameterizedTypeInstantiationSyntax +//@[27:035) | ├─IdentifierSyntax +//@[27:035) | | └─Token(Identifier) |resource| +//@[35:036) | ├─Token(LeftChevron) |<| +//@[36:082) | ├─ParameterizedTypeArgumentSyntax +//@[36:082) | | └─StringSyntax +//@[36:082) | | └─Token(StringComplete) |'Microsoft.Storage/storageAccounts@2022-09-01'| +//@[82:083) | └─Token(RightChevron) |>| +//@[83:085) ├─Token(NewLine) |\n\n| type hello = { -//@[00:0108) ├─TypeDeclarationSyntax -//@[00:0004) | ├─Token(Identifier) |type| -//@[05:0010) | ├─IdentifierSyntax -//@[05:0010) | | └─Token(Identifier) |hello| -//@[11:0012) | ├─Token(Assignment) |=| -//@[13:0108) | └─ObjectTypeSyntax -//@[13:0014) | ├─Token(LeftBrace) |{| -//@[14:0015) | ├─Token(NewLine) |\n| +//@[00:108) ├─TypeDeclarationSyntax +//@[00:004) | ├─Token(Identifier) |type| +//@[05:010) | ├─IdentifierSyntax +//@[05:010) | | └─Token(Identifier) |hello| +//@[11:012) | ├─Token(Assignment) |=| +//@[13:108) | └─ObjectTypeSyntax +//@[13:014) | ├─Token(LeftBrace) |{| +//@[14:015) | ├─Token(NewLine) |\n| @discriminator('hi') -//@[02:0091) | ├─ObjectTypePropertySyntax -//@[02:0022) | | ├─DecoratorSyntax -//@[02:0003) | | | ├─Token(At) |@| -//@[03:0022) | | | └─FunctionCallSyntax -//@[03:0016) | | | ├─IdentifierSyntax -//@[03:0016) | | | | └─Token(Identifier) |discriminator| -//@[16:0017) | | | ├─Token(LeftParen) |(| -//@[17:0021) | | | ├─FunctionArgumentSyntax -//@[17:0021) | | | | └─StringSyntax -//@[17:0021) | | | | └─Token(StringComplete) |'hi'| -//@[21:0022) | | | └─Token(RightParen) |)| -//@[22:0023) | | ├─Token(NewLine) |\n| +//@[02:091) | ├─ObjectTypePropertySyntax +//@[02:022) | | ├─DecoratorSyntax +//@[02:003) | | | ├─Token(At) |@| +//@[03:022) | | | └─FunctionCallSyntax +//@[03:016) | | | ├─IdentifierSyntax +//@[03:016) | | | | └─Token(Identifier) |discriminator| +//@[16:017) | | | ├─Token(LeftParen) |(| +//@[17:021) | | | ├─FunctionArgumentSyntax +//@[17:021) | | | | └─StringSyntax +//@[17:021) | | | | └─Token(StringComplete) |'hi'| +//@[21:022) | | | └─Token(RightParen) |)| +//@[22:023) | | ├─Token(NewLine) |\n| bar: resource<'Astronomer.Astro/organizations@2023-08-01-preview'> -//@[02:0005) | | ├─IdentifierSyntax -//@[02:0005) | | | └─Token(Identifier) |bar| -//@[05:0006) | | ├─Token(Colon) |:| -//@[07:0068) | | └─ParameterizedTypeInstantiationSyntax -//@[07:0015) | | ├─IdentifierSyntax -//@[07:0015) | | | └─Token(Identifier) |resource| -//@[15:0016) | | ├─Token(LeftChevron) |<| -//@[16:0067) | | ├─ParameterizedTypeArgumentSyntax -//@[16:0067) | | | └─StringSyntax -//@[16:0067) | | | └─Token(StringComplete) |'Astronomer.Astro/organizations@2023-08-01-preview'| -//@[67:0068) | | └─Token(RightChevron) |>| -//@[68:0069) | ├─Token(NewLine) |\n| +//@[02:005) | | ├─IdentifierSyntax +//@[02:005) | | | └─Token(Identifier) |bar| +//@[05:006) | | ├─Token(Colon) |:| +//@[07:068) | | └─ParameterizedTypeInstantiationSyntax +//@[07:015) | | ├─IdentifierSyntax +//@[07:015) | | | └─Token(Identifier) |resource| +//@[15:016) | | ├─Token(LeftChevron) |<| +//@[16:067) | | ├─ParameterizedTypeArgumentSyntax +//@[16:067) | | | └─StringSyntax +//@[16:067) | | | └─Token(StringComplete) |'Astronomer.Astro/organizations@2023-08-01-preview'| +//@[67:068) | | └─Token(RightChevron) |>| +//@[68:069) | ├─Token(NewLine) |\n| } -//@[00:0001) | └─Token(RightBrace) |}| -//@[01:0002) ├─Token(NewLine) |\n| +//@[00:001) | └─Token(RightBrace) |}| +//@[01:002) ├─Token(NewLine) |\n| -//@[00:0000) └─Token(EndOfFile) || +//@[00:000) └─Token(EndOfFile) || diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep index 6b8e2225687..8f5a7be1620 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidResourceDerivedTypes_LF/main.tokens.bicep @@ -116,42 +116,42 @@ type thisIsWeird = resource //@[06:07) NewLine |\n| > //@[00:01) RightChevron |>| //@[01:03) NewLine |\n\n| -type shouldWeBlockThis = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> -//@[00:04) Identifier |type| -//@[05:22) Identifier |shouldWeBlockThis| -//@[23:24) Assignment |=| -//@[25:33) Identifier |resource| -//@[33:34) LeftChevron |<| -//@[34:47) StringLeftPiece |'Microsoft.${| -//@[47:56) StringComplete |'Storage'| -//@[56:85) StringRightPiece |}/storageAccounts@2022-09-01'| -//@[85:86) RightChevron |>| -//@[86:88) NewLine |\n\n| +type interpolated = resource<'Microsoft.${'Storage'}/storageAccounts@2022-09-01'> +//@[00:04) Identifier |type| +//@[05:17) Identifier |interpolated| +//@[18:19) Assignment |=| +//@[20:28) Identifier |resource| +//@[28:29) LeftChevron |<| +//@[29:42) StringLeftPiece |'Microsoft.${| +//@[42:51) StringComplete |'Storage'| +//@[51:80) StringRightPiece |}/storageAccounts@2022-09-01'| +//@[80:81) RightChevron |>| +//@[81:83) NewLine |\n\n| -@sealed() // this was offered as a completion +@sealed() //@[00:01) At |@| //@[01:07) Identifier |sealed| //@[07:08) LeftParen |(| //@[08:09) RightParen |)| -//@[45:46) NewLine |\n| -type shouldWeBlockThis2 = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> -//@[00:04) Identifier |type| -//@[05:23) Identifier |shouldWeBlockThis2| -//@[24:25) Assignment |=| -//@[26:34) Identifier |resource| -//@[34:35) LeftChevron |<| -//@[35:81) StringComplete |'Microsoft.Storage/storageAccounts@2022-09-01'| -//@[81:82) RightChevron |>| -//@[82:84) NewLine |\n\n| +//@[09:10) NewLine |\n| +type shouldNotBeSealable = resource<'Microsoft.Storage/storageAccounts@2022-09-01'> +//@[00:04) Identifier |type| +//@[05:24) Identifier |shouldNotBeSealable| +//@[25:26) Assignment |=| +//@[27:35) Identifier |resource| +//@[35:36) LeftChevron |<| +//@[36:82) StringComplete |'Microsoft.Storage/storageAccounts@2022-09-01'| +//@[82:83) RightChevron |>| +//@[83:85) NewLine |\n\n| type hello = { //@[00:04) Identifier |type| diff --git a/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.symbols.bicep index ed07cf0e1ba..0d30411a4cb 100644 --- a/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.symbols.bicep @@ -16,7 +16,7 @@ type sealedString = string @sealed() type sealedDictionary = { -//@[5:21) TypeAlias sealedDictionary. Type: error. Declaration start char: 0, length: 48 +//@[5:21) TypeAlias sealedDictionary. Type: Type<{ *: string }>. Declaration start char: 0, length: 48 *: string } diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 5caa66a1847..5954ad4d6ac 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -2107,6 +2107,11 @@ public FixableDiagnostic ProviderDeclarationViaImportKeywordIsDeprecated(Provide TextSpan, "BCP385", $@"Using resource-derived types requires enabling EXPERIMENTAL feature ""{nameof(ExperimentalFeaturesEnabled.ResourceDerivedTypes)}""."); + + public ErrorDiagnostic DecoratorMayNotTargetResourceDerivedType(string decoratorName) => new( + TextSpan, + "BCP386", + $@"The decorator ""{decoratorName}"" may not be used on statements whose declared type is a reference to a resource-derived type."); } public static DiagnosticBuilderInternal ForPosition(TextSpan span) diff --git a/src/Bicep.Core/LanguageConstants.cs b/src/Bicep.Core/LanguageConstants.cs index 211d1adb3ef..355d61dac73 100644 --- a/src/Bicep.Core/LanguageConstants.cs +++ b/src/Bicep.Core/LanguageConstants.cs @@ -183,6 +183,7 @@ public static class LanguageConstants public const string TypeNameInt = "int"; public const string TypeNameModule = "module"; public const string TypeNameTest = "test"; + public const string TypeNameResource = "resource"; public static readonly StringComparer IdentifierComparer = StringComparer.Ordinal; public static readonly StringComparison IdentifierComparison = StringComparison.Ordinal; diff --git a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs index d87775cc669..549824283a0 100644 --- a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs +++ b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs @@ -1672,7 +1672,24 @@ not WildcardImportSymbol and .WithDescription("Marks an object parameter as only permitting properties specifically included in the type definition") .WithFlags(FunctionFlags.ParameterOutputOrTypeDecorator) .WithAttachableType(LanguageConstants.Object) - .WithValidator(ValidateNotTargetingAlias) + .WithValidator((decoratorName, decoratorSyntax, targetType, typeManager, binder, parsingErrorLookup, diagnosticWriter) => + { + switch (UnwrapNullableSyntax(GetDeclaredTypeSyntaxOfParent(decoratorSyntax, binder))) + { + case VariableAccessSyntax variableAccess when binder.GetSymbolInfo(variableAccess) is not AmbientTypeSymbol: + diagnosticWriter.Write(DiagnosticBuilder.ForPosition(decoratorSyntax).DecoratorMayNotTargetTypeAlias(decoratorName)); + break; + case AccessExpressionSyntax accessExpression when binder.GetSymbolInfo(accessExpression.BaseExpression) is not BuiltInNamespaceSymbol: + diagnosticWriter.Write(DiagnosticBuilder.ForPosition(decoratorSyntax).DecoratorMayNotTargetTypeAlias(decoratorName)); + break; + case ParameterizedTypeInstantiationSyntaxBase parameterized when LanguageConstants.IdentifierComparer.Equals(parameterized.Name.IdentifierName, LanguageConstants.TypeNameResource): + diagnosticWriter.Write(DiagnosticBuilder.ForPosition(decoratorSyntax).DecoratorMayNotTargetResourceDerivedType(decoratorName)); + break; + case ObjectTypeSyntax @object when @object.AdditionalProperties is not null: + diagnosticWriter.Write(DiagnosticBuilder.ForPosition(decoratorSyntax).SealedIncompatibleWithAdditionalPropertiesDeclaration()); + break; + } + }) .WithEvaluator((functionCall, decorated) => { if (decorated is TypeDeclaringExpression typeDeclaringExpression) @@ -1750,19 +1767,19 @@ private static IEnumerable GetBuiltInUtilityTypes(IFeatureProvider { if (features.ResourceDerivedTypesEnabled) { - yield return new("resource", - new TypeTemplate("resource", + yield return new(LanguageConstants.TypeNameResource, + new TypeTemplate(LanguageConstants.TypeNameResource, ImmutableArray.Create(new TypeParameter("ResourceTypeIdentifier", "A string of the format '@' that identifies the kind of resource whose body type definition is to be used.", LanguageConstants.StringResourceIdentifier)), (binder, syntax, argumentTypes) => { - if (argumentTypes.FirstOrDefault() is not StringLiteralType stringLiteral) + if (syntax.Arguments.FirstOrDefault()?.Expression is not StringSyntax stringArg || stringArg.TryGetLiteralValue() is not string resourceTypeString) { return new(DiagnosticBuilder.ForPosition(TextSpan.BetweenExclusive(syntax.OpenChevron, syntax.CloseChevron)).CompileTimeConstantRequired()); } - if (!TypeHelper.GetResourceTypeFromString(binder, stringLiteral.RawStringValue, ResourceTypeGenerationFlags.None, parentResourceType: null) + if (!TypeHelper.GetResourceTypeFromString(binder, resourceTypeString, ResourceTypeGenerationFlags.None, parentResourceType: null) .IsSuccess(out var resourceType, out var errorBuilder)) { return new(errorBuilder(DiagnosticBuilder.ForPosition(syntax.GetArgumentByPosition(0)))); diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 2b2c311e011..ee0a29fcec4 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -411,11 +411,9 @@ private bool GetLengthModifiers(DecorableSyntax syntax, long? defaultMinLength, private TypeSymbol GetModifiedObject(ObjectType declaredObject, DecorableSyntax syntax, TypeSymbolValidationFlags validationFlags) { - if (TryGetSystemDecorator(syntax, LanguageConstants.ParameterSealedPropertyName) is DecoratorSyntax sealedDecorator) + if (TryGetSystemDecorator(syntax, LanguageConstants.ParameterSealedPropertyName) is not null) { - return declaredObject.AdditionalPropertiesFlags.HasFlag(TypePropertyFlags.FallbackProperty) - ? new ObjectType(declaredObject.Name, validationFlags, declaredObject.Properties.Values, additionalPropertiesType: null) - : ErrorType.Create(DiagnosticBuilder.ForPosition(sealedDecorator).SealedIncompatibleWithAdditionalPropertiesDeclaration()); + return new ObjectType(declaredObject.Name, validationFlags, declaredObject.Properties.Values, additionalPropertiesType: null); } if (declaredObject.ValidationFlags == validationFlags) From d4b213e2ec5852a7e23fd96b5018876da1f67d28 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Fri, 5 Jan 2024 17:26:33 -0500 Subject: [PATCH 20/20] Fix failing test --- src/Bicep.LangServer.IntegrationTests/HoverTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Bicep.LangServer.IntegrationTests/HoverTests.cs b/src/Bicep.LangServer.IntegrationTests/HoverTests.cs index b68eb01bce2..2c50a488930 100644 --- a/src/Bicep.LangServer.IntegrationTests/HoverTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/HoverTests.cs @@ -3,12 +3,9 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.Features; using Bicep.Core.Navigation; @@ -35,7 +32,6 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using SymbolKind = Bicep.Core.Semantics.SymbolKind; namespace Bicep.LangServer.IntegrationTests { @@ -80,7 +76,7 @@ public async Task HoveringOverSymbolReferencesAndDeclarationsShouldProduceHovers new List(), (accumulated, node) => { - if (node is ISymbolReference || node is ITopLevelNamedDeclarationSyntax) + if ((node is ISymbolReference @ref && TestSyntaxHelper.NodeShouldBeBound(@ref)) || node is ITopLevelNamedDeclarationSyntax) { accumulated.Add(node); }