Skip to content
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

Compile-time type imports #11298

Merged
merged 30 commits into from
Aug 4, 2023
Merged

Compile-time type imports #11298

merged 30 commits into from
Aug 4, 2023

Conversation

jeskew
Copy link
Contributor

@jeskew jeskew commented Jul 17, 2023

Partially addresses #10121

This PR adds compile-time imports for user-defined types. This feature is only available under a flag and must be enabled with a bicepconfig.json file that looks like this:

{
  "experimentalFeaturesEnabled": {
    "compileTimeImports": true,
    "userDefinedTypes": true
  }
}

With the feature enabled, the import keyword can be used to move user-defined types across templates. For example, given the following template saved as mod.bicep:

@export()
type foo = string

@export()
type bar = int

The foo and bar type symbols can be imported into another template either individually by name using "symbols list" syntax:

import {foo, bar} from 'mod.bicep'

or with "wildcard" syntax (which is less efficient but more fun to say):

import * as myImports from 'mod.bicep'

With the "symbols" list syntax, you can import a subset of the target template's exported symbols and rename/alias them at will:

import {foo} from 'mod.bicep' // <-- Omits bar from the compiled template
import {bar} from 'mod.bicep' // <-- Omits foo from the compiled template
import {
  foo as fizz
  bar as buzz
} from 'mod.bicep'                   // <-- Aliases both foo and bar

You can also mix and match syntaxes. Symbols will be imported at most once:

import {foo} from 'mod.bicep'
import * as baz from 'mod.bicep'

Imported types can be used anywhere a user-defined type might be (i.e., within the type clauses of type, param, and output statements).

Only types that bear the @export() decorator can be imported. As of this PR, this decorator can only be used on type statements.

Microsoft Reviewers: codeflow:open?pullrequest=https://github.com/Azure/bicep/pull/11298&drop=dogfoodAlpha

@jeskew jeskew force-pushed the jeskew/imports branch 3 times, most recently from 37a3d8c to 6012104 Compare July 18, 2023 17:18
@github-actions
Copy link
Contributor

github-actions bot commented Jul 18, 2023

Test Results (linux-x64)

       33 files  ±  0         33 suites  ±0   35m 31s ⏱️ + 2m 7s
10 324 tests +29  10 324 ✔️ +29  0 💤 ±0  0 ±0 
12 541 runs  +29  12 541 ✔️ +29  0 💤 ±0  0 ±0 

Results for commit 41e11e0. ± Comparison against base commit 09d0d4b.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 18, 2023

Test Results (osx-x64)

       33 files  ±  0         33 suites  ±0   1h 25m 48s ⏱️ - 4m 8s
10 328 tests +29  10 328 ✔️ +29  0 💤 ±0  0 ±0 
12 545 runs  +29  12 545 ✔️ +29  0 💤 ±0  0 ±0 

Results for commit 41e11e0. ± Comparison against base commit 09d0d4b.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 18, 2023

Test Results (win-x64)

       33 files  ±  0         33 suites  ±0   41m 3s ⏱️ + 3m 0s
10 336 tests +29  10 336 ✔️ +29  0 💤 ±0  0 ±0 
12 552 runs  +29  12 552 ✔️ +29  0 💤 ±0  0 ±0 

Results for commit 41e11e0. ± Comparison against base commit 09d0d4b.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 18, 2023

Test Results (linux-musl-x64)

       33 files  ±  0         33 suites  ±0   30m 42s ⏱️ - 3m 19s
10 324 tests +29  10 324 ✔️ +29  0 💤 ±0  0 ±0 
12 541 runs  +29  12 541 ✔️ +29  0 💤 ±0  0 ±0 

Results for commit 41e11e0. ± Comparison against base commit 09d0d4b.

♻️ This comment has been updated with latest results.

@jeskew jeskew force-pushed the jeskew/imports branch 2 times, most recently from cc2c513 to 384d8ce Compare July 20, 2023 13:59
@jeskew jeskew changed the title Compile-time imports Compile-time type imports Jul 24, 2023
@jeskew jeskew marked this pull request as ready for review July 24, 2023 21:22
@jeskew jeskew requested a review from a team July 24, 2023 21:22
@shenglol
Copy link
Contributor

I remember in the proposal export is a keyword. Any reason why we switched it to a decorator?

@jeskew
Copy link
Contributor Author

jeskew commented Jul 25, 2023

I remember in the proposal export is a keyword. Any reason why we switched it to a decorator?

It occurred to me while working on the parser that since Bicep already has one means of modifying a statement (decorators), we might not want to introduce a secondary mechanism (auxiliary keywords). Another worry is that a prefix keyword that can be applied to multiple kinds of statements will make dissimilar statements look too much alike, e.g.:

// It's easy to scan the left margin and tell what a statement will do. 
// Even with decorators and some separating spaces, syntax highlighting makes the first keyword pop
@description('The foo param')
param foo = string

@description('The bar output')
output bar = foo

@description('The baz variable')
@export()
var baz = 'baz'

@description('The quux type')
@export()
type quux = int

// My worry is that once `export` is highlighted as a keyword, `export var` will look more like `export type` than like `var`
export var fizz = 'fizz'
export type buzz = 'buzz'

The flip side of this is that an @export() decorator may get buried if the statement has a lot of modifiers:

@description('The combination for a four digit lock. Pad from the left with zeroes to four digits if less than 1000')
@metadata({
  examples: [
    0    // 0000
    10   // 0010
    100  // 0100
    1000 // 1000
  ]
})
@export()
@minValue(0)
@maxValue(9999)
type lockCombination = int

I'll bring this up at a team discussion -- neither approach is problematic to implement, and I'm curious to see what everyone finds easier to read.


namespace Bicep.Core.Syntax;

public class WildcardImportSyntax : SyntaxBase, INamedDeclarationSyntax
Copy link
Contributor

Choose a reason for hiding this comment

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

With import * as foo from 'foo.bicep, will all the members in foo.bicep be put under a namespace foo and can only be accessed via the namespace? If so, do we have plans to add support for wildcard imports without alias (import * from 'foo.bicep') and default imports (import 'foo.bicep')?

Copy link
Contributor

Choose a reason for hiding this comment

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

Default imports is currently being blocked by provider imports, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd prefer to hold off on both default imports and unaliased wildcards. Unaliased wildcards create a situation where your template can start failing to compile because something you're importing added a new symbol whose name clashes with something else defined in your template. Any case where a template starts breaking without changes to the source should be viewed with suspicion IMHO.

For default imports, I'm just not sure if there would be demand. It seems like something we can add later if users ask for it.

@majastrz
Copy link
Member

This produced no diagnostics ("hi.bicep" is empty):

import * as t from 'hi.bicep'

output foo t.fake = ''

Is that expected?

@majastrz
Copy link
Member

Should we have completions for type references?

hi.bicep:

@export()
type fortyTwo = 42

main.bicep:


import * as t from 'hi.bicep'

output foo t.|

Screenshot:
image

@majastrz
Copy link
Member

It would be nice if we could make go to definition work on fortyTwo or hi.bicep (it does work on t). The former may be out of scope of the PR, though.

import * as t from 'hi.bicep'
output foo t.fortyTwo = 3

Comment on lines +310 to +311
var importedFromProperties = ExpressionFactory.CreateObjectProperty(LanguageConstants.ImportMetadataSourceTemplatePropertyName,
ExpressionFactory.CreateStringLiteral(originMetadata.SourceTemplateIdentifier)).AsEnumerable();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm a bit concerned that we might be exposing too much information here if the symbol comes from a local file, a template spec, or a private module registry.

The format used will either be a path relative to the entrypoint model or a fully qualified ts: or br: URI, and the intention is to help with debugging (so that a user can see that a type named 1.foo in their compiled template came from mod.bicep or br:mcr.microsoft.com:network/virtual-network:1.0.1).

This data is also useful for decompilation when the source template is in br/public, but there's always a chance a local file or private registry module could have an embarrassing or too-revealing name, like templates_from_people_I_am_about_to_fire/mod.bicep or br:acr.microsoft.com:companyName/templates_we_stole_from_rival_x/storage:1.0.1

Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I think it would fine to emit the source template information. The the same information already exists in the Bicep file, and the generated template might already contain other confidential data, so emitting the source template information does not inherently introduce new risks. We just need to make sure not to accidentally log the property in the backend.

Copy link
Contributor

@shenglol shenglol left a comment

Choose a reason for hiding this comment

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

🚢

@jeskew jeskew merged commit e39a2f1 into main Aug 4, 2023
52 checks passed
@jeskew jeskew deleted the jeskew/imports branch August 4, 2023 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants