-
Notifications
You must be signed in to change notification settings - Fork 729
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 use of type properties as type expressions #13047
Conversation
Test this change out locally with the following install scripts (Action run 7662066748) VSCode
Azure CLI
|
…arameters and outputs
… within value syntax when determining type
if (!isResourceBodyType) | ||
{ | ||
// strict validation on top-level resource properties, as 'custom' top-level properties are not supported by the platform | ||
return TypeSymbolValidationFlags.Default; | ||
flags |= TypeSymbolValidationFlags.WarnOnPropertyTypeMismatch; | ||
} | ||
|
||
// in all other places, we should allow some wiggle room so that we don't block compilation if there are any swagger inaccuracies | ||
return TypeSymbolValidationFlags.WarnOnTypeMismatch; | ||
// strict validation on top-level resource properties, as 'custom' top-level properties are not supported by the platform | ||
if (!isResourceBodyType && !isResourceBodyTopLevelPropertyType) | ||
{ | ||
// in all other places, we should allow some wiggle room so that we don't block compilation if there are any swagger inaccuracies | ||
flags |= TypeSymbolValidationFlags.WarnOnTypeMismatch; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conceptually, isn't the goal the same (error on top-level properties, warn on everything else)? Why do you need the new flag here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flags are similar but handle slightly different cases. Consider the properties
property on any resource: we know that this must be an object, and since it's part of the ARM RPC contract, we want to emit an error diagnostic if a template supplies a string or array instead of an object, but warn if there's a mismatch within the properties
property:
resource res 'My.RP/widgets@2024-12-31' = {
...
properties: 'foo' // <-- should be an error
}
resource res 'My.RP/widgets@2024-12-31' = {
...
properties: {
stringWithMinLength3: 'ab' // <-- should be a warning (string is too short)
unknownProperty: 'foo' // <-- should be a warning (unexpected property)
}
}
We're currently figuring out whether to warn or error for a property based on the WarnOnTypeMismatch
flag on the enclosing object. So properties: 'foo'
above is an error because the body type for "My.RP/widgets@2024-12-31" resources does not have the WarnOnTypeMismatch
flag, while the issues within properties
are warnings because the properties
property of the resource body type does have that flag. This presented an issue when I wanted to reuse a type that should generate a warning on mismatch (resource<'My.RP/widgets@2024-12-31'>.properties.knownPropertyOfTypeString
) inside of an object that will not have the WarnOnTypeMismatch
flag:
// mod.bicep
param p resource<'My.RP/widgets@2024-12-31'>.properties.stringWithMinLength3
// main.bicep
module mod 'mod.bicep' = {
name: 'mod'
params: {
p: 'ab'
}
}
Since we don't want to set the WarnOnTypeMismatch
flag on the params
property of a module, I needed to update the property validator to look at a flag on the property type rather than on the enclosing object. Because that wouldn't cover unexpected properties, I added a secondary WarnOnPropertyTypeMismatch
flag. For the "My.RP/widgets@2024-12-31" resource, the body type gets no flags, each top level property gets a WarnOnPropertyTypeMismatch
flag, and everything else gets both WarnOnPropertyTypeMismatch
and WarnOnTypeMismatch
:
// these are the flags assigned today
resource res 'My.RP/widgets@2024-12-31' = { // <-- None
name: ... // <-- WarnOnTypeMismatch
location: ... // <-- WarnOnTypeMismatch
properties: { // <-- WarnOnTypeMismatch
property: ... // <-- WarnOnTypeMismatch
}
}
// these are the flags that would be assigned with this PR
resource res 'My.RP/widgets@2024-12-31' = { // <-- None
name: ... // <-- WarnOnPropertyTypeMismatch
location: ... // <-- WarnOnPropertyTypeMismatch
properties: { // <-- WarnOnPropertyTypeMismatch
property: ... // <-- WarnOnPropertyTypeMismatch | WarnOnTypeMismatch
}
}
@@ -13,13 +14,11 @@ namespace Bicep.Core.TypeSystem.Types; | |||
/// </summary> | |||
public interface IUnboundResourceDerivedType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] The 'unbound' terminology has been confusing me (since we already have a separate unrelated concept of binding), and it seems like it's being used to refer specifically just to resource derived types. Have you considered calling this just IResourceDerivedType
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe IUnresolvedResourceDerivedType
is better? At this point, the compiler hasn't yet looked up the type as defined by the provider.
Resolves #12920 Resolves #9229 This PR allows elements or properties of types to be used as standalone type expressions. This feature is compatible with both resource-derived types and compile-time imports, as shown in the following example: types.bicep ```bicep @export() type myObject = { quux: int saSku: resource<'Microsoft.Storage/storageAccounts@2022-09-01'>.sku } ``` main.bicep ```bicep import * as types from 'types.bicep' type test = { baz: types.myObject } type test2 = { foo: { bar: test } } type test3 = test2.foo.bar.baz.quux // ^^ compiles to {"$ref": "#/definitions/_1.myObject/properties/quux"} type test4 = test2.foo.bar.baz.saSku.name // ^^ compiles to: // { // "type": "string", // "metadata": { // "__bicep_resource_derived_type!": "Microsoft.Storage/storageAccounts@2022-09-01#properties/sku/properties/name" // } // } ``` Accessing the following element kinds is supported: * Properties may be dereferenced via dot notation * Tuple items may be dereferenced via array index * An object's additional properties type declaration may be dereferenced via a special `.*` syntax * A typed array's element type declaration may be dereferenced via a special `[*]` syntax For example: ```bicep type anObject = { property: int *: string } type strings = string[] type tuple = [int, string] param propertyDeref anObject.property = 10 // type compiles to {"$ref": "#/definitions/anObject/properties/property"} param additionalPropertiesDeref anObject.* = 'foo' // type compiles to {"$ref": "#/definitions/anObject/additionalProperties"} param elementDeref strings[*] = 'bar' // type compiles to {"$ref": "#/definitions/strings/items"} param itemDeref tuple[1] = 'baz' // type compiles to {"$ref": "#/definitions/tuple/prefixItems/1"} ``` CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/13047)
Resolves #12920
Resolves #9229
This PR allows elements or properties of types to be used as standalone type expressions. This feature is compatible with both resource-derived types and compile-time imports, as shown in the following example:
types.bicep
main.bicep
Accessing the following element kinds is supported:
.*
syntax[*]
syntaxFor example:
Microsoft Reviewers: Open in CodeFlow