Skip to content

Proposal - Better validation for conditional resources/modules #3750

Open
@anthony-c-martin

Description

@anthony-c-martin

Proposal - Better validation for conditional resources/modules

Problem statement

Dealing with conditional resources/modules today can be error prone as you must remember to propagate conditions correctly, and the type system will not alert you to this being done incorrectly - leading to deploy-time errors.

In the following sample, there are no Bicep errors although the output statement may be invalid if someCond is false and the resource does not exist.

param someCond bool

resource resWithCond 'Microsoft.Compute/virtualMachines@2021-03-01' existing = if (someCond) {
  name: 'abc'
}

output myValue string = resWithCond.properties.licenseType

To fix this, the output statement needs to be changed to the following, to propagate the conditional check:

output myValue string = someCond ? resWithCond.properties.licenseType : ''

I've mentioned this idea on a few other issues, but thought it would be a good idea to write it up formally for tracking.

Potential Solutions

Note - these solutions all build on-top of each other - 3 depends on 1 & 2, and 2 depends on 1.

1. Treat conditional references as resource | null

In the first example, if resWithCond has type resource | null, accessing properties directly will raise an error, so the user will be forced to handle the null-ness and write something like the following:

output myValue string = someCond ? resWithCond.properties.licenseType : ''

There are still problems with this approach - it is a little verbose, and Bicep needs to validate that the condition in the ternary is exactly the same as the condition on the resource.

2. Introduce 'null-conditional' operator .?

This builds upon the previous solution, but also adds a new operator which removes the need to propate the someCond condition - so the type error would be fixable with:

output myValue string = resWithCond?.properties.licenseType ?? ''

In the generated JSON output, Bicep would generate a ternary if() statement, bringing the condition for the nullable reference forward:
[if(<someCond>, <resWithCond reference statement>, '')]

This is essentially syntactic sugar for 1., but means that lengthy conditions don't need to be copy-pasted.

3. Going deeper with conditional flow analysis

Again, this builds on 1. & 2., giving Bicep the ability to skip null-checks if the condition has already been checked:

param someCond bool

resource resWithCond 'Microsoft.Compute/virtualMachines@2021-03-01' existing = if (someCond) {
  name: 'abc'
}

resource someOtherRes 'My.Rp/madeUpResource@2021-03-01' = if (someCond) {
  name: 'someOtherRes '
  properties: {
    // the .? and ?? may be skipped because we're inside a block which already has a `if (someCond)` check on it
    someProp: resWithCond.properties.licenseType
  }
}

output myObj object = someCond ? {
  // again, we don't need to handle nullability here because we've already checked for someCond
  someProp: resWithCond.properties.licenseType
} : {}

output myOtherObj object = {
  // we do need to handle nullability here, because we haven't already checked for someCond
  someProp: resWithCond?.properties.licenseType ?? ''
}

Other Notes

  1. Any solution for stacking conditions will need to be robust to handle binary bool operations - e.g. inside a block which checks someCond && otherCond - our flow analysis will need to understand that a nullable reference gated on someCond can be used safely, and inside a block which checks someCond || otherCond it cannot be.
  2. A solution will also need to be robust enough to perform analysis on assignment through variables - e.g. var combinedCond = someCond && otherCond

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions