Skip to content

Conversation

@tsmallig33
Copy link
Contributor

@tsmallig33 tsmallig33 commented Dec 15, 2025

Description

Adds nullable existing resource support with @nullIfNotFound decorator. Note, @nullIfNotFound() will not work until backend changes have been deployed

Example Usage

@nullIfNotFound()
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-04-01' existing = {
  name: 'testStorage'
}

output safeLocation string? = storageAccount.?location
output safeSkuName string? = storageAccount.?sku.name
output safeAccessTier string? = storageAccount.?properties.accessTier

output locationWithDefault string = storageAccount.?location ?? 'westus'
output skuNameWithDefault string = storageAccount.?sku.name ?? 'Standard_LRS'
output accessTierWithDefault string = storageAccount.?properties.accessTier ?? 'Hot'

Checklist

Microsoft Reviewers: Open in CodeFlow

@github-actions
Copy link
Contributor

github-actions bot commented Dec 15, 2025

Test this change out locally with the following install scripts (Action run 20277551735)

VSCode
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-vsix.sh) --run-id 20277551735
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-vsix.ps1) } -RunId 20277551735"
Azure CLI
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-cli.sh) --run-id 20277551735
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-cli.ps1) } -RunId 20277551735"

@github-actions
Copy link
Contributor

github-actions bot commented Dec 15, 2025

Dotnet Test Results

   102 files   -     51     102 suites   - 51   39m 47s ⏱️ - 19m 52s
12 592 tests +     2  12 592 ✅ +     2  0 💤 ±0  0 ❌ ±0 
28 929 runs   - 14 413  28 929 ✅  - 14 413  0 💤 ±0  0 ❌ ±0 

Results for commit 0c7e587. ± Comparison against base commit 56292d6.

This pull request removes 1956 and adds 676 tests. Note that renamed tests count towards both.

		nestedProp1: 1
		nestedProp2: 2
		prop1: true
		prop2: false
	1
	2
	\$'")
	prop1: true
	prop2: false
…
Bicep.Cli.UnitTests.Services.ReplEnvironmentTests ‑ ShouldSubmitBuffer_terminates_at_expected_point ("var foo = {
")
Bicep.Cli.UnitTests.Services.ReplEnvironmentTests ‑ ShouldSubmitBuffer_terminates_at_expected_point ("var multilineString = '''
Line 1
Line 2
Line 3
'''")
Bicep.Cli.UnitTests.Services.ReplEnvironmentTests ‑ ShouldSubmitBuffer_terminates_at_expected_point ("var outRoleAssignments object[] = union(map(
  filter(varMockedEntraGroupIds, item => !contains(item.uniqueName, 'DevOps')),
  group => {
    principalId: group.groupId
    definition: group.roleToAssign
    relativeScope: ''
    principalType: 'Group'
  }
),[
  {
    principalId: '22222222-2222-2222-2222-222222222222'
    definition: 'Reader'
    relativeScope: ''
    principalType: 'ServicePrincipal'
  }
])")
Bicep.Cli.UnitTests.Services.ReplEnvironmentTests ‑ ShouldSubmitBuffer_terminates_at_expected_point ("var test = {
  abc: 'def' // boo
}")
Bicep.Cli.UnitTests.Services.ReplEnvironmentTests ‑ ShouldSubmitBuffer_terminates_at_expected_point ("var varMockedEntraGroupIds = [
  {
    uniqueName: 'Reader-Group'
    roleToAssign: 'Reader'
    groupId: '11111111-1111-1111-1111-111111111111'
  }
  {
    uniqueName: 'Contributor-Group'
    roleToAssign: 'Contributor'
    groupId: '22222222-2222-2222-2222-222222222222'
  }
  {
    uniqueName: 'DevOps-Group'
    groupId: '33333333-3333-3333-3333-333333333333'
  }
]")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000
�ӽ
�0\u0010\u0007��\u0014�\u000f�^�&i��.��\u000f\u0010퉊��\u001fP\u0010�]t\u0011���ch~������ϣ���r�\u001b.�I"\u000ecCD4q�MD\u001c&���ykQ\u0001�a\u0006]Ӻ\u001a\u0010a�D�*�^�k�S�oͣ\u000cd��ZP&��)�$Mxl�TF\u0005K����w�����\u0018�\u0016\u0012�0�J@�Y\u00006K'�u����CI}E���\u001d\u0015G��
;�{C��o�<��`2op'}�\u0000\u000c\u0000\u0000,"Value cannot be null. (Parameter 'source')")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000
��;
�0\u0010\u0004Э}
�@���s�>e� �!\u001f,\u0007��!��T�M\u001a�.��L���22?��P�S��R\u0019�sIK\u0003\u0000��'\u0001�\u0013�<�w�Y\u0012\u0013�`��\u0011@��X��p��x��#�k��Ly�\u000c����YS���K��h���\u000b'���;����C\u0011\u001bV(�Z�\u0008��1�U69�{����
�$I\u0012��\u001bN��@\u0000\u000c\u0000\u0000,"The path: index.json was not found in artifact contents")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000
��=
�@\u0010\u0005�=��\u00016o7�N"�\u0010,��
��\u0004\u00120BT�\u0003\u0001//ڈilb\u0004ݯy�\u0014\u0003�\u001b�l�v�%iVVR�&p$�
\u0000��=\u0012@7\u0001�;��pI�4�����\u0000�O*\u0014��>D�)�Zy�Χ�\u000eDQ�E\u0016)6~\u0008\u000e�@z\u0006ЬG�^������1o��=R��p�1�	��\u0013H\u000c�����\u001dǫ�t\u0019����z5�\u0008��glY�e�/�\u0001(2BP\u0000\u000c\u0000\u0000,"'7' is an invalid end of a number. Expected a delimiter. Path: $.INVALID_JSON | LineNumber: 0 | BytePositionInLine: 20.")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000\u0003��=
�@\u0010\u0005�=�'X�캳Zا�\u0015\u0016"�\u0007��\u000f\u0011B�\u001eH\u0015l�\u0018-ܯy�\u0014\u000f�����\u000e�?�]��.8S�4\u0000�<�$�y\u0002³{'�(�h\u0005c?��\u0000�'���\u000f�j\u000c��>���mHt�6å�+vbK8a�Lila\�u�hQ���?���hb�\u001a�E\u001bK`\u0018�)]e����|m� ��(�
�\u0001\u0003���\u0000\u000c\u0000\u0000,"The path: index.json was not found in artifact contents")
Bicep.Core.IntegrationTests.AzTypesViaRegistryTests ‑ Bicep_compiler_handles_corrupted_extension_package_gracefully (\u001f�\u0008\u0000\u0000\u0000\u0000\u0000\u0000\u0003��=
�@\u0010\u0005�=��\u00016o���D��\u0011l<�jFTL�$�xw�F���p��5S<\u0018��\u0016!�$�����%g��
\u0000���\u0004�@���T�/8�C�\u0008��ĕ:�a;ݵ�D���(/U3�\u001a��wE\u0005��h�J\u0007�G��������1/�o,q�9\u000c��\u0014\u0004\u0006������������V�Iփ�siVҍ'j\u0013\u000e�\��-I�$���\u0001�+f@\u0000\u000c\u0000\u0000,"Value cannot be null. (Parameter 'source')")
…

♻️ This comment has been updated with latest results.

@tsmallig33 tsmallig33 added 📘 Docs Not Needed do not merge Do not merge this pull request yet. labels Dec 16, 2025
@tsmallig33 tsmallig33 marked this pull request as ready for review December 16, 2025 15:58
@tsmallig33 tsmallig33 requested a review from jeskew December 16, 2025 15:59
Comment on lines 93 to 98
/// <summary>
/// Checks if the syntax has a decorator with the specified name.
/// </summary>
public static bool HasDecorator(this DecorableSyntax syntax, string decoratorName)
=> syntax.Decorators.Any(d => d.Expression is FunctionCallSyntax functionCall && functionCall.NameEquals(decoratorName));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decorators can be fully qualified, so you can't always make this determination based on syntax alone. There is a helper method that handles instance function calls, but you would need to call it with the model:

public static DecoratorSyntax? TryGetDecoratorInNamespace(SemanticModel semanticModel, DecorableSyntax syntax, string @namespace, string decoratorName)
=> TryGetDecoratorInNamespace(semanticModel.Binder, semanticModel.TypeManager.GetDeclaredType, syntax, @namespace, decoratorName);
public static DecoratorSyntax? TryGetDecoratorInNamespace(IBinder binder, Func<SyntaxBase, TypeSymbol?> getDeclaredTypeFunc, DecorableSyntax syntax, string @namespace, string decoratorName)
{
return syntax.Decorators.FirstOrDefault(decorator =>
{
if (SymbolHelper.TryGetSymbolInfo(binder, getDeclaredTypeFunc, decorator.Expression) is not FunctionSymbol functionSymbol ||
functionSymbol.DeclaringObject is not NamespaceType namespaceType)
{
return false;
}
return LanguageConstants.IdentifierComparer.Equals(namespaceType.ExtensionName, @namespace) &&
LanguageConstants.IdentifierComparer.Equals(functionSymbol.Name, decoratorName);
});
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed from SyntaxExtensions and added TryGetDecoratorInNamespace to IsResourceEnabled

private bool? IsResourceEnabled(ResourceSymbol resource)
{
// Nullable existing resources may not exist at deployment time
if (resource.DeclaringResource.IsNullableExistingResource())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only place .IsNullableExistingResource() is called? If so, you could simplify a bit by doing the decorator check here.

@tsmallig33 tsmallig33 force-pushed the tasmalligan/AddNullableExistingDecorator branch from e9e6057 to 0c7e587 Compare December 16, 2025 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge Do not merge this pull request yet. 📘 Docs Not Needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants