Skip to content

Commit 6c8a3f9

Browse files
authored
Validate that type expressions that will be compiled to ARM schema nodes can be expressed in ARM's type system prior to compilation (#15901)
Resolves #15277 ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/15901)
1 parent 2808f38 commit 6c8a3f9

File tree

4 files changed

+79
-4
lines changed

4 files changed

+79
-4
lines changed

src/Bicep.Core.IntegrationTests/UserDefinedTypeTests.cs

+24
Original file line numberDiff line numberDiff line change
@@ -1838,4 +1838,28 @@ public void Parameterized_type_recursion_raises_diagnostic(string template)
18381838
("BCP298", DiagnosticLevel.Error, "This type definition includes itself as required component, which creates a constraint that cannot be fulfilled."),
18391839
});
18401840
}
1841+
1842+
// https://www.github.com/Azure/bicep/issues/15277
1843+
[DataTestMethod]
1844+
[DataRow("type resourceDerived = resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings")]
1845+
[DataRow("param resourceDerived resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings")]
1846+
[DataRow("output resourceDerived resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings = 'foo'")]
1847+
[DataRow("type t = { property: resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings }")]
1848+
[DataRow("type t = { *: resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings }")]
1849+
[DataRow("type t = [ resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings ]")]
1850+
[DataRow("type t = resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings[]")]
1851+
[DataRow("func f() resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings => 'foo'")]
1852+
[DataRow("func f(p resourceInput<'Microsoft.Compute/virtualMachines/extensions@2019-12-01'>.properties.settings) string => 'foo'")]
1853+
public void Type_expressions_that_will_become_ARM_schema_nodes_are_checked_for_ARM_type_system_compatibility_prior_to_compilation(string template)
1854+
{
1855+
var result = CompilationHelper.Compile(
1856+
new ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true)),
1857+
template);
1858+
1859+
result.Template.Should().BeNull();
1860+
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new[]
1861+
{
1862+
("BCP411", DiagnosticLevel.Error, """The type "any" cannot be used in a type assignment because it does not fit within one of ARM's primitive type categories (string, int, bool, array, object). If this is a resource type definition inaccuracy, report it using https://aka.ms/bicep-type-issues."""),
1863+
});
1864+
}
18411865
}

src/Bicep.Core.Samples/Files/baselines/InvalidTypeDeclarations_LF/main.diagnostics.bicep

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type nullLiteral = null
3838
//@[19:023) [BCP289 (Error)] The type definition is not valid. (bicep https://aka.ms/bicep/core-diagnostics#BCP289) |null|
3939

4040
type unionOfNulls = null|null
41+
//@[20:029) [BCP411 (Error)] The type "null" cannot be used in a type assignment because it does not fit within one of ARM's primitive type categories (string, int, bool, array, object). If this is a resource type definition inaccuracy, report it using https://aka.ms/bicep-type-issues. (bicep https://aka.ms/bicep/core-diagnostics#BCP411) |null|null|
4142
//@[20:029) [BCP294 (Error)] Type unions must be reducible to a single ARM type (such as 'string', 'int', or 'bool'). (bicep https://aka.ms/bicep/core-diagnostics#BCP294) |null|null|
4243

4344
@minLength(3)

src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs

+4
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,10 @@ public Diagnostic ResourceParameterizedTypeIsDeprecated(ParameterizedTypeInstant
18821882
}
18831883

18841884
public Diagnostic AttemptToDivideByZero() => CoreError("BCP410", "Division by zero is not supported.");
1885+
1886+
public Diagnostic TypeExpressionResolvesToUnassignableType(TypeSymbol type) => CoreError(
1887+
"BCP411",
1888+
$"The type \"{type}\" cannot be used in a type assignment because it does not fit within one of ARM's primitive type categories (string, int, bool, array, object).{TypeInaccuracyClause}");
18851889
}
18861890

18871891
public static DiagnosticBuilderInternal ForPosition(TextSpan span)

src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs

+50-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Concurrent;
55
using System.Collections.Immutable;
6+
using Azure.Deployments.Core.Diagnostics;
67
using Azure.Deployments.Templates.Export;
78
using Bicep.Core.Diagnostics;
89
using Bicep.Core.Emit;
@@ -216,13 +217,15 @@ VariableBlockSyntax block when this.binder.GetParent(block) is LambdaSyntax lamb
216217
});
217218

218219
public override void VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax)
219-
=> AssignType(syntax, () =>
220+
=> AssignTypeWithDiagnostics(syntax, diagnostics =>
220221
{
221222
if (typeManager.GetDeclaredType(syntax) is not { } declaredType)
222223
{
223224
return ErrorType.Empty();
224225
}
225226

227+
diagnostics.WriteMultiple(ValidateTypeAssignability(syntax.Type, declaredType));
228+
226229
base.VisitTypedLocalVariableSyntax(syntax);
227230

228231
return declaredType;
@@ -521,9 +524,10 @@ public override void VisitTypeDeclarationSyntax(TypeDeclarationSyntax syntax)
521524

522525
if (declaredType is not null)
523526
{
524-
ValidateDecorators(syntax.Decorators,
525-
declaredType is TypeType wrapped ? wrapped.Unwrapped : declaredType,
526-
diagnostics);
527+
var unwrapped = declaredType is TypeType wrapped ? wrapped.Unwrapped : declaredType;
528+
ValidateDecorators(syntax.Decorators, unwrapped, diagnostics);
529+
530+
diagnostics.WriteMultiple(ValidateTypeAssignability(syntax.Value, unwrapped));
527531
}
528532

529533
return declaredType ?? ErrorType.Empty();
@@ -600,6 +604,8 @@ public override void VisitArrayTypeMemberSyntax(ArrayTypeMemberSyntax syntax)
600604

601605
base.VisitArrayTypeMemberSyntax(syntax);
602606

607+
diagnostics.WriteMultiple(ValidateTypeAssignability(syntax.Value, declaredType));
608+
603609
return declaredType;
604610
});
605611

@@ -878,6 +884,7 @@ private TypeSymbol GetDeclaredTypeAndValidateDecorators(DecorableSyntax targetSy
878884
}
879885

880886
this.ValidateDecorators(targetSyntax.Decorators, declaredType, diagnostics);
887+
diagnostics.WriteMultiple(ValidateTypeAssignability(typeSyntax, declaredType));
881888

882889
return declaredType;
883890
}
@@ -1958,6 +1965,8 @@ public override void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax)
19581965
CollectErrors(errors, argumentType.Type);
19591966
}
19601967

1968+
diagnostics.WriteMultiple(ValidateTypeAssignability(syntax.ReturnType, declaredLambdaType.ReturnType.Type));
1969+
19611970
var returnType = TypeValidator.NarrowTypeAndCollectDiagnostics(typeManager, binder, this.parsingErrorLookup, diagnostics, syntax.Body, declaredLambdaType.ReturnType.Type);
19621971
CollectErrors(errors, returnType);
19631972

@@ -2396,6 +2405,43 @@ private IEnumerable<IDiagnostic> ValidateDefaultValue(ParameterDefaultValueSynta
23962405
return diagnosticWriter.GetDiagnostics();
23972406
}
23982407

2408+
private IEnumerable<IDiagnostic> ValidateTypeAssignability(SyntaxBase typeSyntax, TypeSymbol assignedType)
2409+
{
2410+
if (typeSyntax is not SkippedTriviaSyntax &&
2411+
assignedType is not ErrorType &&
2412+
TryGetArmPrimitiveType(assignedType, typeSyntax) is null)
2413+
{
2414+
yield return DiagnosticBuilder.ForPosition(typeSyntax)
2415+
.TypeExpressionResolvesToUnassignableType(assignedType);
2416+
}
2417+
}
2418+
2419+
private TypeSymbol? TryGetArmPrimitiveType(TypeSymbol type, SyntaxBase syntax) => type switch
2420+
{
2421+
BooleanLiteralType or BooleanType => LanguageConstants.Bool,
2422+
IntegerLiteralType or IntegerType => LanguageConstants.Int,
2423+
StringLiteralType or StringType => LanguageConstants.String,
2424+
ResourceType when features.ResourceTypedParamsAndOutputsEnabled => LanguageConstants.String,
2425+
ObjectType or DiscriminatedObjectType => LanguageConstants.Object,
2426+
TupleType or ArrayType => LanguageConstants.Array,
2427+
UnionType when TypeHelper.TryRemoveNullability(type) is { } nonNull => TryGetArmPrimitiveType(nonNull, syntax),
2428+
UnionType when IsExplicitUnion(syntax) => LanguageConstants.Any,
2429+
UnionType union when union.Members.Select(m => TryGetArmPrimitiveType(m.Type, syntax)).ToArray() is { } mTypes &&
2430+
!mTypes.Any(t => t is null) &&
2431+
mTypes.ToHashSet() is { } mUniqueTypes &&
2432+
mUniqueTypes.Count == 1 => mUniqueTypes.Single(),
2433+
_ => null,
2434+
};
2435+
2436+
private static bool IsExplicitUnion(SyntaxBase syntax) => syntax switch
2437+
{
2438+
UnionTypeSyntax => true,
2439+
ParenthesizedTypeSyntax parenthesized => IsExplicitUnion(parenthesized.Expression),
2440+
NonNullableTypeSyntax nonNullable => IsExplicitUnion(nonNullable.Base),
2441+
NullableTypeSyntax nullable => IsExplicitUnion(nullable.Base),
2442+
_ => false,
2443+
};
2444+
23992445
private IEnumerable<IDiagnostic> ValidateIdentifierAccess(SyntaxBase syntax)
24002446
{
24012447
return SyntaxAggregator.Aggregate(syntax, new List<IDiagnostic>(), (accumulated, current) =>

0 commit comments

Comments
 (0)