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 function imports #12221

Merged
merged 9 commits into from
Oct 25, 2023
Merged

Compile-time function imports #12221

merged 9 commits into from
Oct 25, 2023

Conversation

jeskew
Copy link
Contributor

@jeskew jeskew commented Oct 22, 2023

This PR allows user-defined functions to be reused across templates.

First, a function needs to be declared with an @export() decorator:

@export()
func greet(name string) string => 'Hi, ${name}!'

Next, another template can import this function by name:

import {greet} from 'other.bicep'

var greeting = greet('buddy') // <-- greeting == 'Hi, buddy!'

or as part of a namespace:

import * as foo from 'other.bicep'

var greeting = foo.greet('buddy') // <-- greeting == 'Hi, buddy!'

If the imported function uses user-defined types or calls other functions, those types and/or functions will be pulled into the importing template as part of the imported function's closure (but will not create symbols in the importing template's namespace unless they are explicitly imported).

To support importing functions from modules published to a registry, functions can also be imported from ARM JSON templates. User-defined functions in Bicep templates will always end up in the __bicep namespace and can be imported by the function name; user-defined functions in other namespaces may be imported using their fully qualified name (<namespace>.<function name>) and must be aliased in the importing template. For example, given the following ARM JSON template:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "functions": [
        {
            "namespace": "__bicep",
            "members": {
                "greet": {
                    "parameters": [
                        {
                            "type": "string",
                            "name": "name"
                        }
                    ],
                    "output": {
                        "type": "string",
                        "value": "[format('Hi, {0}!', parameters('name'))]"
                    },
                    "metadata": {
                        "__bicep_export!": true
                    }
                }
            }
        },
        {
            "namespace": "ns",
            "members": {
                "echo": {
                    "parameters": [
                        {
                            "type": "string",
                            "name": "in"
                        }
                    ],
                    "output": {
                        "type": "string",
                        "value": "[parameters('in')]"
                    },
                    "metadata": {
                        "__bicep_export!": true
                    }
                }
            }
        }
    ],
    "resources": []
}

Both functions may be imported into a Bicep template (since both are marked as exportable) using the following syntax:

import {
  greet,
  'ns.echo' as echo
} from 'template.json'

Because function symbols in Bicep need to implement a specific interface in order to be callable, this PR requires that imported symbols be distinguishable by C# type; therefore, the ImportedSymbol class has been replaced with ImportedTypeSymbol, ImportedVariableSymbol, ImportedFunctionSymbol, and ErroredImportSymbol. In order to make this distinction during the symbol declaration phase of compilation (when access to the ISymbolContext is blocked), a special ISemanticModelLookup is passed to the DeclarationVisitor that allows access semantic models other than the one being compiled. The SourceFileGroupingBuilder class blocks import cycles, so this is expected to be safe.

Microsoft Reviewers: Open in CodeFlow

@github-actions
Copy link
Contributor

github-actions bot commented Oct 22, 2023

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

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

@github-actions
Copy link
Contributor

github-actions bot commented Oct 22, 2023

Test Results

     132 files  ±  0       132 suites  ±0   3h 57m 43s ⏱️ + 8m 4s
10 668 tests +12  10 668 ✔️ +12  0 💤 ±0  0 ±0 
51 559 runs  +48  51 559 ✔️ +48  0 💤 ±0  0 ±0 

Results for commit 7a9742b. ± Comparison against base commit 561bb59.

♻️ This comment has been updated with latest results.

@jeskew jeskew marked this pull request as ready for review October 24, 2023 15:51
switch (syntax.ImportExpression)
{
case WildcardImportSyntax wildcardImport:
DeclareSymbol(new WildcardImportSymbol(context, model, wildcardImport, syntax));
Copy link
Member

Choose a reason for hiding this comment

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

I like this pattern - is it possible to do something similar for modules?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is proving a bit involved (largely due to the need to update tests), so I'm going to tackle in a separate PR.

exports.AddRange(@namespace.Members.Where(kvp => IsExported(kvp.Value))
.Select(kvp => new ExportedFunctionMetadata(
Name: $"{namePrefix}{kvp.Key}",
Parameters: kvp.Value.Parameters is TemplateFunctionParameter[] parameters
Copy link
Member

Choose a reason for hiding this comment

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

[nit] can shorten to kvp.Value.Parameters is {} parameters, if you're just trying to check non-null.

Copy link
Member

@anthony-c-martin anthony-c-martin 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 enabled auto-merge (squash) October 25, 2023 15:33
@jeskew jeskew merged commit e256fc9 into main Oct 25, 2023
46 checks passed
@jeskew jeskew deleted the jeskew/function-imports branch October 25, 2023 16:04
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

2 participants