Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow dot property access on union of objects with undeclared property #14060

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/Bicep.Core.IntegrationTests/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6053,4 +6053,93 @@ func genModuleTags(moduleName string) moduleTags => {
["name name"] = "name",
});
}

[TestMethod]
public void Test_Issue14059_repro1()
{
// https://github.com/Azure/bicep/issues/14059
var result = CompilationHelper.Compile("""
param foo string
param index int

var test = [
{ foo: foo }
{ }
][index]

output val string = test.foo
""");

result.Should().NotHaveAnyDiagnostics();
}

[TestMethod]
public void Test_Issue14059_repro2()
{
// https://github.com/Azure/bicep/issues/14059
var result = CompilationHelper.Compile("""
var items = [
{ obj: 1 }
{ obj: 2 }
{ obj: 3 }
{ obj: 4 }
{ obj: 5, x: 5 }
]
var s = sort(
filter(
map(items, x => x.obj < 2 ? { order: 1, value: x } : x.obj > 4 ? { order: 2, value: x } : {}),
x => !(empty(x))
),
(arg1, arg2) => arg1.order < arg2.order
)
""");

result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
}

[TestMethod]
public void Test_Issue14059_repro3()
{
// https://github.com/Azure/bicep/issues/14059
var result = CompilationHelper.Compile("""
param isProd bool
param location string

var appConfigValues = [
{
Key: 'KEY1'
Value: isProd ? 'URL1' : 'URL2'
}
{
Key: 'InvoicingImports:AzureBlobStorageImport:ContainerName'
Value: 'ikros'
}
isProd ? {
Key: 'ServiceDiscovery:FunctionApps:OpenApi:Url'
Value: 'https://kros-esw-prod-openapi-azfun.azurewebsites.net'
} : {}
]

resource appConfiguration 'Microsoft.AppConfiguration/configurationStores@2022-05-01' = {
name: 'settings-config'
location: location
sku: {
name: 'standard'
}
properties: {
softDeleteRetentionInDays: 7
}
}

resource addAppConfigValues 'Microsoft.AppConfiguration/configurationStores/keyValues@2022-05-01' = [for i in range(0, length(appConfigValues)) : {
name: !empty(appConfigValues[i]) ? '${appConfigValues[i].Key}' : 'notExisting'
parent: appConfiguration
properties: {
value: appConfigValues[i].Value
}
}]
""");

result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
}
}
37 changes: 35 additions & 2 deletions src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,40 @@ private TypeSymbol GetAccessedType(AccessExpressionSyntax syntax, IDiagnosticWri
: baseType;
}

private static TypeSymbol? TryGetReadablePropertyType(ObjectType objectType, string propertyName)
{
if (objectType.Properties.TryGetValue(propertyName) is {} property && !property.Flags.HasFlag(TypePropertyFlags.WriteOnly))
{
return property.TypeReference.Type;
}

if (objectType.AdditionalPropertiesType is {} additionalPropertiesType && !objectType.AdditionalPropertiesFlags.HasFlag(TypePropertyFlags.WriteOnly))
{
return additionalPropertiesType.Type;
}

return null;
}

private static TypeSymbol GetNamedPropertyType(UnionType unionType, string propertyName)
{
var members = new List<TypeSymbol>();
foreach (var member in unionType.Members)
{
if (member is not ObjectType objectType ||
TryGetReadablePropertyType(objectType, propertyName) is not {} propertyType)
{
// fall back to any if we can't definitively obtain the property type.
// this may give some false positives - we can further refine this if desired.
return LanguageConstants.Any;
}

members.Add(propertyType);
}

return TypeHelper.CreateTypeUnion(members);
}

private static TypeSymbol GetNamedPropertyType(PropertyAccessSyntax syntax, TypeSymbol baseType, IDiagnosticWriter diagnostics) => UnwrapType(baseType) switch
{
ErrorType error => error,
Expand All @@ -1802,8 +1836,7 @@ TypeSymbol original when TypeHelper.TryRemoveNullability(original) is TypeSymbol
syntax.IsSafeAccess || TypeValidator.ShouldWarnForPropertyMismatch(objectType),
diagnostics),

UnionType unionType when unionType.Members.All(x => x is ObjectType)
=> TypeHelper.CreateTypeUnion(unionType.Members.Select(member => GetNamedPropertyType(syntax, member.Type, diagnostics))),
UnionType unionType when syntax.PropertyName.IsValid => GetNamedPropertyType(unionType, syntax.PropertyName.IdentifierName),

// TODO: We might be able use the declared type here to resolve discriminator to improve the assigned type
DiscriminatedObjectType => LanguageConstants.Any,
Expand Down
Loading