diff --git a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs index 9cae140dc9d..442bcaffe7a 100644 --- a/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs +++ b/src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs @@ -1823,4 +1823,19 @@ param siteProperties resourceInput<'Microsoft.Web/sites@2022-09-01'>.properties """The property "availabilityState" is read-only. Expressions cannot be assigned to read-only properties.""" ); } + + [DataTestMethod] + [DataRow("type resourceInput = resourceInput<'Microsoft.Compute/virtualMachines'>")] // should be caught at syntax level + [DataRow("type resourceInput = resourceInput<'Microsoft.Compute/virtualMachines'>.properties")] // should be caught by type manager + public void Parameterized_type_recursion_raises_diagnostic(string template) + { + var result = CompilationHelper.Compile( + new ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true)), + template); + + result.Should().HaveDiagnostics(new[] + { + ("BCP298", DiagnosticLevel.Error, "This type definition includes itself as required component, which creates a constraint that cannot be fulfilled."), + }); + } } diff --git a/src/Bicep.Core/TypeSystem/CyclicCheckVisitor.cs b/src/Bicep.Core/TypeSystem/CyclicCheckVisitor.cs index 6fc9cddfca5..fc3986b12ab 100644 --- a/src/Bicep.Core/TypeSystem/CyclicCheckVisitor.cs +++ b/src/Bicep.Core/TypeSystem/CyclicCheckVisitor.cs @@ -167,6 +167,18 @@ public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax) base.VisitFunctionCallSyntax(syntax); } + public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + { + if (!currentDeclarations.TryPeek(out var currentDeclaration)) + { + // we're not inside a declaration, so there should be no risk of a cycle + return; + } + + declarationAccessDict[currentDeclaration].Add(syntax); + base.VisitParameterizedTypeInstantiationSyntax(syntax); + } + public override void VisitArrayTypeMemberSyntax(ArrayTypeMemberSyntax syntax) => WithSelfReferencePermitted(() => base.VisitArrayTypeMemberSyntax(syntax), selfReferencePermitted: true); diff --git a/src/Bicep.Core/TypeSystem/CyclicTypeCheckVisitor.cs b/src/Bicep.Core/TypeSystem/CyclicTypeCheckVisitor.cs index 8c1c02e51b1..422eb30d8bd 100644 --- a/src/Bicep.Core/TypeSystem/CyclicTypeCheckVisitor.cs +++ b/src/Bicep.Core/TypeSystem/CyclicTypeCheckVisitor.cs @@ -72,6 +72,18 @@ public override void VisitTypeVariableAccessSyntax(TypeVariableAccessSyntax synt base.VisitTypeVariableAccessSyntax(syntax); } + public override void VisitParameterizedTypeInstantiationSyntax(ParameterizedTypeInstantiationSyntax syntax) + { + // If this reference is not nested within a type container, it would have been detected based on syntax alone in CyclicCheckVisitor. + // To avoid doubling up on diagnostics, skip recording cycles on top-level accesses + if (enteredTypeContainer) + { + declarationAccesses.Add(syntax); + } + + base.VisitParameterizedTypeInstantiationSyntax(syntax); + } + public override void VisitArrayTypeSyntax(ArrayTypeSyntax syntax) => WithEnteredTypeContainerState(() => base.VisitArrayTypeSyntax(syntax), enteredTypeContainer: true); diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 1fcef5662aa..1ac7501b94f 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -259,9 +259,6 @@ private DeclaredTypeAssignment GetTypeType(TypeDeclarationSyntax syntax) private TypeSymbol GetUserDefinedTypeType(TypeAliasSymbol symbol) { - // Even if the declared type is invalid because of a illegal cycle, we still want to visit (and cache the type of) nested elements. - var declaredType = GetTypeFromTypeSyntax(symbol.DeclaringType.Value); - if (binder.TryGetCycle(symbol) is { } cycle) { var builder = DiagnosticBuilder.ForPosition(symbol.DeclaringType.Name); @@ -272,7 +269,9 @@ private TypeSymbol GetUserDefinedTypeType(TypeAliasSymbol symbol) return ErrorType.Create(diagnostic); } - return ApplyTypeModifyingDecorators(DisallowNamespaceTypes(declaredType.Type, symbol.DeclaringType.Value), symbol.DeclaringType); + return ApplyTypeModifyingDecorators( + DisallowNamespaceTypes(GetTypeFromTypeSyntax(symbol.DeclaringType.Value).Type, symbol.DeclaringType.Value), + symbol.DeclaringType); } private static ITypeReference DisallowNamespaceTypes(ITypeReference typeReference, SyntaxBase syntax) => typeReference switch