bicep icon indicating copy to clipboard operation
bicep copied to clipboard

Type system reflection error

Open itpropro opened this issue 3 years ago • 3 comments

Bicep version Bicep CLI version 0.7.4 (5afc312467)

Describe the bug I use modules from the CARML and my own and they all use the following to generate managed identity objects to be used in the template:

@description('Optional. Enables system assigned managed identity on the resource.')
param systemAssignedIdentity bool = false

@description('Optional. The ID(s) to assign to the resource.')
param userAssignedIdentities object = {}
...
var identityType = systemAssignedIdentity ? (!empty(userAssignedIdentities) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned') : (!empty(userAssignedIdentities) ? 'UserAssigned' : 'None')

var identity = identityType != 'None' ? {
  type: identityType
  userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null
} : null

If I have a submodule where I just want to hand over the generated identity object instead of copying the same logic again, I get the following error:

module xyz 'xyz' = [for element in elements: {
  name: element.name
  params: {
    ...
    identity: !empty(identity) ? identity : {}
    ...
  }
}]
// Error:
var identity: null | object
The property "identity" expected a value of type "object" but the provided value is of type "null | object | object".bicep(BCP036)

That error is correct, but when I update the statement to reflect the null state:

identity: ((identity != null) && !empty(identity)) ? identity : {}

It still throws the same error: The property "identity" expected a value of type "object" but the provided value is of type "null | object | object".bicep(BCP036)

To Reproduce Use the above code

image

itpropro avatar Jun 18 '22 19:06 itpropro

@itpropro

If you are looking for a way to make this scenario work to pass to the Module, you can use an alternate default than null.

@description('Optional. Enables system assigned managed identity on the resource.')
param systemAssignedIdentity bool = false

@description('Optional. The ID(s) to assign to the resource.')
param userAssignedIdentities object = {}

var identityType = systemAssignedIdentity ? (!empty(userAssignedIdentities) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(userAssignedIdentities) ? 'UserAssigned' : 'None')

var identity = identityType != 'None' ? {
  type: identityType
  userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null
} : {}  // <-- default to empty object instead

module xyz 'xyx.bicep' = {
  name: 'myModule01'
  params: {
    identity: identity
    location: resourceGroup().location
  }
}

Then inside of the Module, have the default, plus if it's empty convert it back to null.

xyx.bicep

param identity object = {}

@description('Required. Name of the static site.')
@minLength(1)
@maxLength(40)
param name string = 'mystaticTest54'

@allowed([
  'Free'
  'Standard'
])
@description('Optional. Type of static site to deploy.')
param sku string = 'Free'

@description('Location to deploy static site. The following locations are supported: CentralUS, EastUS2, EastAsia, WestEurope, WestUS2.')
param location string

resource staticSite 'Microsoft.Web/staticSites@2021-03-01' = {
  name: name
  location: location
  identity: identity == {} ? null : identity
  sku: {
    name: sku
    tier: sku
  }
  properties: {}
}

brwilkinson avatar Jun 23 '22 17:06 brwilkinson

Thanks @brwilkinson for the suggestion, I already use that workaround at many points. But mostly it's just a workaround for missing features/bugs in the reflection system. With the statement identity: ((identity != null) && !empty(identity)) ? identity : {}, the Bicep compiler should definitely know that the value can't be null and thereby adjusting it internal type representation, as e.g. TypeScript would do.

itpropro avatar Jun 25 '22 12:06 itpropro

The particular scenario outlined will now emit a warning rather than an error, and there are a couple of ways to manually suppress it. You can use a non-null assertion to tell the compiler it's wrong and that the value won't be null:

module xyz 'xyz' = [for element in elements: {
  name: element.name
  params: {
    ...
    identity: !empty(identity) ? identity! : {}
    ...
  }
}]

or you can disable the type mismatch diagnostic altogether (NB: #disable-next-line will only work on warnings, not errors):

module xyz 'xyz' = [for element in elements: {
  name: element.name
  params: {
    ...
    #disable-next-line BCP321
    identity: !empty(identity) ? identity : {}
    ...
  }
}]

The compiler could infer that identity won't be null from context, but this is not yet implemented. (tracked in #12121)

jeskew avatar Oct 16 '23 20:10 jeskew