in src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs [1317:1433]
public override void VisitObjectSyntax(ObjectSyntax syntax)
=> AssignType(syntax, () =>
{
foreach (var missingDeclarationSyntax in syntax.Children.OfType<MissingDeclarationSyntax>())
{
VisitMissingDeclarationSyntax(missingDeclarationSyntax);
}
var errors = new List<IDiagnostic>();
var duplicatedProperties = syntax.Properties
.GroupByExcludingNull(prop => prop.TryGetKeyText(), LanguageConstants.IdentifierComparer)
.Where(group => group.Count() > 1);
foreach (var group in duplicatedProperties)
{
foreach (ObjectPropertySyntax duplicatedProperty in group)
{
errors.Add(DiagnosticBuilder.ForPosition(duplicatedProperty.Key).PropertyMultipleDeclarations(group.Key));
}
}
var childTypes = new List<TypeSymbol>();
foreach (var child in syntax.Children)
{
var childType = child switch
{
ObjectPropertySyntax x => typeManager.GetTypeInfo(x),
SpreadExpressionSyntax x => typeManager.GetTypeInfo(x.Expression),
_ => null,
};
if (childType is null)
{
continue;
}
if (child is SpreadExpressionSyntax spread &&
childType is not ErrorType &&
!TypeValidator.AreTypesAssignable(childType, LanguageConstants.Object))
{
childType = ErrorType.Create(DiagnosticBuilder.ForPosition(child).SpreadOperatorRequiresAssignableValue(spread, LanguageConstants.Object));
}
CollectErrors(errors, childType);
childTypes.Add(childType);
}
if (PropagateErrorType(errors, childTypes))
{
return ErrorType.Create(errors);
}
// Discriminated objects should have been resolved by the declared type manager.
var declaredType = typeManager.GetDeclaredType(syntax);
var namedProperties = new Dictionary<string, NamedTypeProperty>(LanguageConstants.IdentifierComparer);
var additionalProperties = new List<TypeSymbol>();
foreach (var child in syntax.Children)
{
if (child is ObjectPropertySyntax propertySyntax)
{
var resolvedType = typeManager.GetTypeInfo(propertySyntax);
if (propertySyntax.TryGetKeyText() is { } name)
{
if (declaredType is ObjectType objectType && objectType.Properties.TryGetValue(name, out var property))
{
// we've found a declared object type for the containing object, with a matching property name definition.
// preserve the type property details (name, descriptions etc.), and update the assigned type.
// Since this type corresponds to a value that is being supplied, make sure it has the `Required` flag and does not have the `.ReadOnly` flag
namedProperties[name] = new NamedTypeProperty(
property.Name,
resolvedType,
(property.Flags | TypePropertyFlags.Required) & ~TypePropertyFlags.ReadOnly,
property.Description);
}
else
{
// we've not been able to find a declared object type for the containing object, or it doesn't contain a property matching this one.
// best we can do is to simply generate a property for the assigned type.
namedProperties[name] = new NamedTypeProperty(name, resolvedType, TypePropertyFlags.Required);
}
}
else
{
additionalProperties.Add(resolvedType);
}
}
if (child is SpreadExpressionSyntax spreadSyntax)
{
var type = typeManager.GetTypeInfo(spreadSyntax.Expression);
if (type is ObjectType spreadType)
{
foreach (var (name, property) in spreadType.Properties)
{
namedProperties[name] = property;
}
if (spreadType.AdditionalProperties is { } spreadTypeAdditionalProperties)
{
additionalProperties.Add(spreadTypeAdditionalProperties.TypeReference.Type);
}
}
else
{
additionalProperties.Add(LanguageConstants.Any);
}
}
}
var additionalPropertiesType = additionalProperties.Any() ? TypeHelper.CreateTypeUnion(additionalProperties) : null;
// TODO: Add structural naming?
return new ObjectType(LanguageConstants.Object.Name, TypeSymbolValidationFlags.Default, namedProperties.Values, additionalPropertiesType is null ? null : new(additionalPropertiesType));
});