pulumi/pkg/compiler/binder.go

970 lines
33 KiB
Go
Raw Normal View History

// Copyright 2016 Marapongo, Inc. All rights reserved.
package compiler
import (
"fmt"
"reflect"
"regexp"
"strings"
"unicode/utf8"
"github.com/golang/glog"
"github.com/marapongo/mu/pkg/ast"
"github.com/marapongo/mu/pkg/ast/conv"
"github.com/marapongo/mu/pkg/compiler/core"
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/errors"
"github.com/marapongo/mu/pkg/util/contract"
)
// Binder annotates an existing parse tree with semantic information.
type Binder interface {
core.Phase
// PrepareStack prepares the AST for binding. It returns a list of all unresolved dependency references. These
// must be bound and supplied to the BindStack function as the deps argument.
PrepareStack(stack *ast.Stack) []ast.Ref
// BindStack takes an AST, and its set of dependencies, and binds all names inside, mutating it in place. It
// returns a full list of all dependency Stacks that this Stack depends upon (which must then be bound).
BindStack(stack *ast.Stack, deprefs ast.DependencyRefs) []*ast.Stack
// ValidateStack runs last, after all transitive dependencies have been bound, to perform last minute validation.
ValidateStack(stack *ast.Stack)
}
func NewBinder(c Compiler) Binder {
// Create a new binder and a new scope with an empty symbol table.
b := &binder{c: c}
b.PushScope()
return b
}
type binder struct {
c Compiler
scope *scope
}
func (b *binder) Diag() diag.Sink {
return b.c.Diag()
}
func (b *binder) PrepareStack(stack *ast.Stack) []ast.Ref {
glog.Infof("Preparing Mu Stack: %v", stack.Name)
if glog.V(2) {
defer glog.V(2).Infof("Preparing Mu Stack %v completed w/ %v warnings and %v errors",
stack.Name, b.Diag().Warnings(), b.Diag().Errors())
}
// Push a new scope for this binding pass.
b.PushScope()
// Now perform a phase1 walk of the tree, preparing it for subsequent binding. This must be done as a
// separate phase because we won't know what to stick into the symbol table until after this first walk.
phase := newBinderPreparePhase(b, stack)
v := core.NewInOrderVisitor(phase, nil)
v.VisitStack(stack)
// Return a set of dependency references that must be loaded before BindStack occurs.
return phase.deps
}
func (b *binder) BindStack(stack *ast.Stack, deprefs ast.DependencyRefs) []*ast.Stack {
glog.Infof("Binding Mu Stack: %v", stack.Name)
if glog.V(2) {
defer glog.V(2).Infof("Binding Mu Stack %v completed w/ %v warnings and %v errors",
stack.Name, b.Diag().Warnings(), b.Diag().Errors())
}
// Now perform a phase2 walk of the tree, completing the binding process. The 1st walk will have given
// us everything we need for a fully populated symbol table, so that type binding will resolve correctly.
phase := newBinderBindPhase(b, stack, deprefs)
v := core.NewInOrderVisitor(phase, nil)
v.VisitStack(stack)
return phase.deps
}
func (b *binder) ValidateStack(stack *ast.Stack) {
glog.Infof("Validating Mu Stack: %v", stack.Name)
if glog.V(2) {
defer glog.V(2).Infof("Validating Mu Stack %v completed w/ %v warnings and %v errors",
stack.Name, b.Diag().Warnings(), b.Diag().Errors())
}
// Restore the original scope after this binding pass.
defer b.PopScope()
// Now perform the final validation of the AST. There's nothing to return, it just may issue errors.
phase := newBinderValidatePhase(b)
v := core.NewInOrderVisitor(phase, nil)
v.VisitStack(stack)
}
// LookupService binds a name to a Service type.
func (b *binder) LookupService(nm ast.Name) (*ast.Service, bool) {
contract.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupService")
return b.scope.LookupService(nm)
}
// LookupStack binds a name to a Stack type.
func (b *binder) LookupStack(nm ast.Name) (*ast.Stack, bool) {
contract.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupStack")
return b.scope.LookupStack(nm)
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
// LookupUninstStack binds a name to a UninstStack type.
func (b *binder) LookupUninstStack(nm ast.Name) (*ast.UninstStack, bool) {
contract.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupUninstStack")
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
return b.scope.LookupUninstStack(nm)
}
// LookupSchema binds a name to a Schema type.
func (b *binder) LookupSchema(nm ast.Name) (*ast.Schema, bool) {
contract.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupSchema")
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
return b.scope.LookupSchema(nm)
}
// LookupSymbol binds a name to any kind of Symbol.
func (b *binder) LookupSymbol(nm ast.Name) (*Symbol, bool) {
contract.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupSymbol")
return b.scope.LookupSymbol(nm)
}
// RegisterSymbol registers a symbol with the given name; if it already exists, the function returns false.
func (b *binder) RegisterSymbol(sym *Symbol) bool {
contract.AssertM(b.scope != nil, "Unexpected empty binding scope during RegisterSymbol")
return b.scope.RegisterSymbol(sym)
}
// PushScope creates a new scope with an empty symbol table parented to the existing one.
func (b *binder) PushScope() {
b.scope = &scope{parent: b.scope, symtbl: make(map[ast.Name]*Symbol)}
}
// PopScope replaces the current scope with its parent.
func (b *binder) PopScope() {
contract.AssertM(b.scope != nil, "Unexpected empty binding scope during pop")
b.scope = b.scope.parent
}
// scope enables lookups and symbols to obey traditional language scoping rules.
type scope struct {
parent *scope
symtbl map[ast.Name]*Symbol
}
// LookupService binds a name to a Service type.
func (s *scope) LookupService(nm ast.Name) (*ast.Service, bool) {
sym, exists := s.LookupSymbol(nm)
if exists && sym.Kind == SymKindService {
return sym.Real.(*ast.Service), true
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
// LookupStack binds a name to a Stack type.
func (s *scope) LookupStack(nm ast.Name) (*ast.Stack, bool) {
sym, exists := s.LookupSymbol(nm)
if exists && sym.Kind == SymKindStack {
return sym.Real.(*ast.Stack), true
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
// LookupUninstStack binds a name to a UninstStack type.
func (s *scope) LookupUninstStack(nm ast.Name) (*ast.UninstStack, bool) {
sym, exists := s.LookupSymbol(nm)
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
if exists && sym.Kind == SymKindUninstStack {
return sym.Real.(*ast.UninstStack), true
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
// LookupSchema binds a name to a Schema type.
func (s *scope) LookupSchema(nm ast.Name) (*ast.Schema, bool) {
sym, exists := s.LookupSymbol(nm)
if exists && sym.Kind == SymKindSchema {
return sym.Real.(*ast.Schema), true
}
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
return nil, false
}
// LookupSymbol binds a name to any kind of Symbol.
func (s *scope) LookupSymbol(nm ast.Name) (*Symbol, bool) {
for s != nil {
if sym, exists := s.symtbl[nm]; exists {
return sym, true
}
s = s.parent
}
return nil, false
}
// RegisterSymbol registers a symbol with the given name; if it already exists, the function returns false.
func (s *scope) RegisterSymbol(sym *Symbol) bool {
nm := sym.Name
if _, exists := s.symtbl[nm]; exists {
// TODO: this won't catch "shadowing" for parent scopes; do we care about this?
return false
}
s.symtbl[nm] = sym
return true
}
type binderPreparePhase struct {
b *binder
top *ast.Stack
deps []ast.Ref
depsm map[ast.Ref]bool
}
var _ core.Visitor = (*binderPreparePhase)(nil) // compile-time assertion that the binder implements core.Visitor.
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
func newBinderPreparePhase(b *binder, top *ast.Stack) *binderPreparePhase {
return &binderPreparePhase{
b: b,
top: top,
deps: make([]ast.Ref, 0),
depsm: make(map[ast.Ref]bool),
}
}
func (p *binderPreparePhase) Diag() diag.Sink {
return p.b.Diag()
}
func (p *binderPreparePhase) VisitWorkspace(workspace *ast.Workspace) {
}
func (p *binderPreparePhase) VisitCluster(name string, cluster *ast.Cluster) {
}
func (p *binderPreparePhase) VisitDependency(parent *ast.Workspace, ref ast.Ref, dep *ast.Dependency) {
// Workspace dependencies must use legal version specs; validate that this parses now so that we can use it
// later on without needing to worry about additional validation.
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
_, err := ref.Parse()
if err != nil {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorIllegalNameLikeSyntax.At(parent), ref, err)
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
}
}
func (p *binderPreparePhase) VisitStack(stack *ast.Stack) {
// If the stack has a base type, we must add it as a bound dependency.
if stack.Base != "" {
p.registerDependency(stack, stack.Base)
}
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
// Stack names are required.
if stack.Name == "" {
p.Diag().Errorf(errors.ErrorMissingStackName.At(stack))
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
}
// Stack versions must be valid semantic versions (and specifically, not ranges). In other words, we need
// a concrete version number like "1.3.9-beta2" and *not* a range like ">1.3.9".
// TODO: should we require a version number?
if stack.Version != "" {
if err := stack.Version.Check(); err != nil {
p.Diag().Errorf(errors.ErrorIllegalStackVersion.At(stack), stack.Version, err)
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
}
}
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
func (p *binderPreparePhase) VisitSchemas(parent *ast.Stack, schemas *ast.Schemas) {
}
func (p *binderPreparePhase) VisitSchema(pstack *ast.Stack, parent *ast.Schemas, name ast.Name,
public bool, schema *ast.Schema) {
// If the schema has an unresolved base type, add it as a bound dependency.
if schema.BoundBase != nil && schema.BoundBase.IsUnresolvedRef() {
p.registerDependency(pstack, *schema.BoundBase.Unref)
}
// Add this schema to the symbol table so that this stack can reference it.
sym := NewSchemaSymbol(schema.Name, schema)
if !p.b.RegisterSymbol(sym) {
p.Diag().Errorf(errors.ErrorSymbolAlreadyExists.At(pstack), sym.Name)
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
func (p *binderPreparePhase) VisitProperty(parent *ast.Stack, schema *ast.Schema, name string, prop *ast.Property) {
// For properties whose types represent stack types, register them as a dependency.
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
if prop.BoundType.IsUnresolvedRef() {
p.registerDependency(parent, *prop.BoundType.Unref)
}
}
func (p *binderPreparePhase) VisitServices(parent *ast.Stack, svcs *ast.Services) {
}
func (p *binderPreparePhase) VisitService(pstack *ast.Stack, parent *ast.Services, name ast.Name,
public bool, svc *ast.Service) {
// Each service has a type. There are two forms of specifying a type, and this phase will normalize this to a
// single canonical form to simplify subsequent phases. First, there is a shorthand form:
//
// private:
// acmecorp/db:
// ...
//
// In this example, "acmecorp/db" is the type and the name is shortened to just "db". Second, there is a longhand
// form for people who want more control over the naming of their services:
//
// private:
// customers:
// type: acmecorp/db
// ...
//
// In this example, "acmecorp/db" is still the type, however the name is given the nicer name of "customers."
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
simplify := false
if svc.Type == "" {
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
svc.Type = ast.Ref(svc.Name)
simplify = true
}
// Remember this service's type as a stack that must be bound later on.
ty, ok := p.registerDependency(pstack, svc.Type)
if !ok {
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
return
}
// If we used the simple form, we must now propagate the friendly name over to the service's name.
if simplify {
svc.Name = ty.Name.Simple()
}
// Add this service to the symbol table so that other service definitions can refer to it by name.
sym := NewServiceSymbol(svc.Name, svc)
if !p.b.RegisterSymbol(sym) {
p.Diag().Errorf(errors.ErrorSymbolAlreadyExists.At(pstack), sym.Name)
}
}
// registerDependency adds a dependency that needs to be resolved/bound before phase 2 occurs.
func (p *binderPreparePhase) registerDependency(stack *ast.Stack, ref ast.Ref) (ast.RefParts, bool) {
ty, err := ref.Parse()
if err == nil {
// First see if this resolves to a stack. If it does, it's already in scope; nothing more to do.
nm := ty.Name
if _, exists := p.b.LookupStack(nm); !exists {
// Otherwise, we need to track this as a dependency to resolve. Make sure to canonicalize the key so that
// we don't end up with duplicate semantically equivalent dependency references.
key := ty.Defaults().Ref()
if _, exist := p.depsm[key]; !exist {
// Store these in an array so that the order is deterministic. But use a map to avoid duplicates.
p.deps = append(p.deps, key)
p.depsm[key] = true
}
}
return ty, true
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorIllegalNameLikeSyntax.At(stack), ref, err)
return ty, false
}
type binderBindPhase struct {
b *binder
top *ast.Stack // the top-most stack being bound.
deps []*ast.Stack // a set of dependencies instantiated during this binding phase.
}
var _ core.Visitor = (*binderBindPhase)(nil) // compile-time assertion that the binder implements core.Visitor.
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
func newBinderBindPhase(b *binder, top *ast.Stack, deprefs ast.DependencyRefs) *binderBindPhase {
p := &binderBindPhase{b: b, top: top}
// Populate the symbol table with this Stack's bound dependencies so that any type lookups are found.
for _, ref := range ast.StableDependencyRefs(deprefs) {
dep := deprefs[ref]
contract.Assert(dep.Doc != nil)
nm := refToName(ref)
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
sym := NewUninstStackSymbol(nm, dep)
if !p.b.RegisterSymbol(sym) {
p.Diag().Errorf(errors.ErrorSymbolAlreadyExists.At(dep.Doc), nm)
}
}
return p
}
func (p *binderBindPhase) Diag() diag.Sink {
return p.b.Diag()
}
func (p *binderBindPhase) VisitWorkspace(workspace *ast.Workspace) {
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
}
func (p *binderBindPhase) VisitCluster(name string, cluster *ast.Cluster) {
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
}
func (p *binderBindPhase) VisitDependency(parent *ast.Workspace, ref ast.Ref, dep *ast.Dependency) {
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
}
func (p *binderBindPhase) VisitStack(stack *ast.Stack) {
// Ensure the name of the base is in scope, and remember the binding information.
if stack.Base != "" {
// TODO[marapongo/mu#7]: we need to plumb construction properties for this stack.
stack.BoundBase = p.ensureStack(stack.Base, nil)
}
// Non-abstract Stacks must declare at least one Service.
Introduce intrinsic types This change eliminates the special type mu/extension in favor of extensible intrinsic types. This subsumes the previous functionality while also fixing a number of warts with the old model. In particular, the old mu/extension approach deferred property binding until very late in the compiler. In fact, too late. The backend provider for an extension simply received an untyped bag of stuff, which it then had to deal with. Unfortunately, some operations in the binder are inaccessible at this point because doing so would cause a cycle. Furthermore, some pertinent information is gone at this point, like the scopes and symtables. The canonical example where we need this is binding services names to the services themselves; e.g., the AWS CloudFormation "DependsOn" property should resolve to the actual service names, not the string values. In the limit, this requires full binding information. There were a few solutions I considered, including ones that've required less code motion, however this one feels the most elegant. Now we permit types to be marked as "intrinsic." Binding to these names is done exactly as ordinary name binding, unlike the special mu/extension provider name. In fact, just about everything except code-generation for these types is the same as ordinary types. This is perfect for the use case at hand, which is binding properties. After this change, for example, "DependsOn" is expanded to real service names precisely as we need. As part of this change, I added support for three new basic schema types: * ast.StringList ("string[]"): a list of strings. * ast.StringMap ("map[string]any"): a map of strings to anys. * ast.ServiceList ("service[]"): a list of service references. Obviously we need to revisit this and add a more complete set. This work is already tracked by marapongo/mu#9. At the end of the day, it's likely I will replace all hard-coded predefined types with intrinsic types, for similar reasons to the above.
2016-12-05 22:46:18 +01:00
if !stack.Intrinsic && !stack.Abstract && len(stack.Services.Public) == 0 && len(stack.Services.Private) == 0 {
p.Diag().Errorf(errors.ErrorNonAbstractStacksMustDefineServices.At(stack))
}
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
func (p *binderBindPhase) VisitSchemas(parent *ast.Stack, schemas *ast.Schemas) {
}
func (p *binderBindPhase) VisitSchema(pstack *ast.Stack, parent *ast.Schemas, name ast.Name,
public bool, schema *ast.Schema) {
// Ensure the base schema is available to us.
if schema.BoundBase != nil && schema.BoundBase.IsUnresolvedRef() {
ref := *schema.BoundBase.Unref
base := p.ensureType(ref)
// Check to ensure that the base is of one of the legal kinds.
if !base.IsPrimitive() && !base.IsSchema() {
p.Diag().Errorf(errors.ErrorSchemaTypeExpected, ref, base)
}
}
// TODO: ensure that schemas with constraints don't have illegal constraints (wrong type; regex won't parse; etc).
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
func (p *binderBindPhase) VisitProperty(parent *ast.Stack, schema *ast.Schema, name string, prop *ast.Property) {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
// For properties whose types represent unresolved names, we must bind them to a name now.
if prop.BoundType.IsUnresolvedRef() {
prop.BoundType = p.ensureType(*prop.BoundType.Unref)
contract.Assert(prop.BoundType != nil)
}
}
func (p *binderBindPhase) VisitServices(parent *ast.Stack, svcs *ast.Services) {
}
func (p *binderBindPhase) VisitService(pstack *ast.Stack, parent *ast.Services, name ast.Name, public bool,
Implement dependency versions This change implements dependency versions, including semantic analysis, per the checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0. There's quite a bit in here but at a top-level this parses and validates dependency references of the form [[proto://]base.url]namespace/.../name[@version] and verifies that the components are correct, as well as binding them to symbols. These references can appear in two places at the moment: * Service types. * Cluster dependencies. As part of this change, a number of supporting changes have been made: * Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis. This allows us to share logic around the validation of common AST types. This also moves some of the logic around loading workspace.yaml files back to the parser, where it can be unified with the way we load Mu.yaml files. * New ast.Version and ast.VersionSpec types. The former represents a precise version -- either a specific semantic version or a short or long Git SHA hash -- and the latter represents a range -- either a Version, "latest", or a semantic range. * New ast.Ref and ast.RefParts types. The former is an unparsed string that is thought to contain a Ref, while the latter is a validated Ref that has been parsed into its components (Proto, Base, Name, and Version). * Added some type assertions to ensure certain structs implement certain interfaces, to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.) * Be consistent about prefixing error types with Error or Warning. * Organize the core compiler driver's logic into three methods, FE, sema, and BE. * A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
svc *ast.Service) {
// The service's type has been prepared in phase 1, and must now be bound to a symbol. All shorthand type
// expressions, intra stack references, cycles, and so forth, will have been taken care of by this earlier phase.
contract.AssertMF(svc.Type != "",
"Expected all Services to have types in binding phase2; %v is missing one", svc.Name)
svc.BoundType = p.ensureStack(svc.Type, svc.Properties)
// A service cannot instantiate an abstract stack.
if svc.BoundType != nil && svc.BoundType.Abstract {
p.Diag().Errorf(errors.ErrorCannotCreateAbstractStack.At(pstack), svc.Name, svc.BoundType.Name)
}
}
// ensureStack binds a ref to a stack symbol, possibly instantiating it if needed.
func (p *binderBindPhase) ensureStack(ref ast.Ref, props ast.PropertyBag) *ast.Stack {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
ty := p.ensureType(ref)
// There are two possibilities. The first is that a type resolves to an *ast.Stack. That's simple, we just fetch
// and return it. The second is that a type resolves to a *diag.Document. That's more complex, as we need to
// actually parse the stack from a document, supplying properties, etc., for template expansion.
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
if ty.IsStack() {
return ty.Stack
} else if ty.IsUninstStack() {
// We have the dependency's Mufile; now we must "instantiate it", by parsing it and returning the result. Note
// that this will be processed later on in semantic analysis, to ensure semantic problems are caught.
pa := NewParser(p.b.c)
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
stack := pa.ParseStack(ty.UninstStack.Doc, props)
if !pa.Diag().Success() {
// If we failed to parse the stack, there was something wrong with our dependency information. Bail out.
return nil
}
p.deps = append(p.deps, stack)
return stack
} else {
p.Diag().Errorf(errors.ErrorStackTypeExpected, ref, ty)
return nil
}
}
// ensureStackType looks up a ref, either as a stack, document, or schema symbol, and returns it as-is.
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
func (p *binderBindPhase) ensureType(ref ast.Ref) *ast.Type {
nm := refToName(ref)
stack, exists := p.b.LookupStack(nm)
if exists {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
return ast.NewStackType(stack)
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
stref, exists := p.b.LookupUninstStack(nm)
if exists {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
return ast.NewUninstStackType(stref)
}
schema, exists := p.b.LookupSchema(nm)
if exists {
return ast.NewSchemaType(schema)
}
contract.FailMF("Expected 1st pass of binding to guarantee type %v exists (%v)", ref, nm)
return nil
}
type binderValidatePhase struct {
b *binder
}
var _ core.Visitor = (*binderValidatePhase)(nil) // compile-time assertion that the binder implements core.Visitor.
func newBinderValidatePhase(b *binder) *binderValidatePhase {
return &binderValidatePhase{b: b}
}
func (p *binderValidatePhase) Diag() diag.Sink {
return p.b.Diag()
}
func (p *binderValidatePhase) VisitWorkspace(workspace *ast.Workspace) {
}
func (p *binderValidatePhase) VisitCluster(name string, cluster *ast.Cluster) {
}
func (p *binderValidatePhase) VisitDependency(parent *ast.Workspace, ref ast.Ref, dep *ast.Dependency) {
}
func (p *binderValidatePhase) VisitStack(stack *ast.Stack) {
if stack.PropertyValues != nil {
// Bind property values.
stack.BoundPropertyValues = p.bindProperties(&stack.Node, stack.Properties, stack.PropertyValues)
}
if stack.Base != "" {
contract.Assert(stack.BoundBase != nil)
// TODO[marapongo/mu#7]: validate the properties from this stack on the base.
}
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
func (p *binderValidatePhase) VisitSchemas(parent *ast.Stack, schemas *ast.Schemas) {
}
func (p *binderValidatePhase) VisitSchema(pstack *ast.Stack, parent *ast.Schemas, name ast.Name,
public bool, schema *ast.Schema) {
}
func (p *binderValidatePhase) VisitProperty(parent *ast.Stack, schema *ast.Schema, name string, prop *ast.Property) {
}
func (p *binderValidatePhase) VisitServices(parent *ast.Stack, svcs *ast.Services) {
}
func (p *binderValidatePhase) VisitService(pstack *ast.Stack, parent *ast.Services, name ast.Name, public bool,
svc *ast.Service) {
contract.Assert(svc.BoundType != nil)
if svc.BoundType.PropertyValues == nil {
// For some types, there aren't any property values (e.g., built-in types). For those, bind now.
svc.BoundProperties = p.bindProperties(&pstack.Node, svc.BoundType.Properties, svc.Properties)
} else {
// For imported types, we should have property values, which already got bound in an earlier phase.
contract.Assert(svc.BoundType.BoundPropertyValues != nil)
contract.Assert(len(svc.BoundType.PropertyValues) == len(svc.Properties))
svc.BoundProperties = svc.BoundType.BoundPropertyValues
}
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
// bindProperties typechecks a set of unbounded properties against the target stack, and expands them into a bag
// of bound properties (with AST nodes rather than the naked parsed types).
func (p *binderValidatePhase) bindProperties(node *ast.Node, props ast.Properties,
vals ast.PropertyBag) ast.LiteralPropertyBag {
bound := make(ast.LiteralPropertyBag)
// First, enumerate all known properties on the stack. Ensure all required properties are present, expand default
// values for missing ones where applicable, and check that types are correct, converting them as appropriate.
for _, pname := range ast.StableProperties(props) {
prop := props[pname]
// First see if a value has been supplied by the caller.
val, has := vals[pname]
if !has || val == nil {
if prop.Default != nil {
// If the property has a default value, stick it in and process it normally.
val = prop.Default
} else if prop.Optional {
// If this is an optional property, ok, just skip the remainder of processing.
continue
} else {
// If there's no value, no default, and it isn't optional, issue an error and move on.
p.Diag().Errorf(errors.ErrorMissingRequiredProperty.At(node), pname)
continue
}
}
contract.Assert(val != nil)
// Now, value in hand, let's make sure it's the right type.
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
if lit := p.bindValue(&prop.Node, val, prop.BoundType); lit != nil {
Introduce intrinsic types This change eliminates the special type mu/extension in favor of extensible intrinsic types. This subsumes the previous functionality while also fixing a number of warts with the old model. In particular, the old mu/extension approach deferred property binding until very late in the compiler. In fact, too late. The backend provider for an extension simply received an untyped bag of stuff, which it then had to deal with. Unfortunately, some operations in the binder are inaccessible at this point because doing so would cause a cycle. Furthermore, some pertinent information is gone at this point, like the scopes and symtables. The canonical example where we need this is binding services names to the services themselves; e.g., the AWS CloudFormation "DependsOn" property should resolve to the actual service names, not the string values. In the limit, this requires full binding information. There were a few solutions I considered, including ones that've required less code motion, however this one feels the most elegant. Now we permit types to be marked as "intrinsic." Binding to these names is done exactly as ordinary name binding, unlike the special mu/extension provider name. In fact, just about everything except code-generation for these types is the same as ordinary types. This is perfect for the use case at hand, which is binding properties. After this change, for example, "DependsOn" is expanded to real service names precisely as we need. As part of this change, I added support for three new basic schema types: * ast.StringList ("string[]"): a list of strings. * ast.StringMap ("map[string]any"): a map of strings to anys. * ast.ServiceList ("service[]"): a list of service references. Obviously we need to revisit this and add a more complete set. This work is already tracked by marapongo/mu#9. At the end of the day, it's likely I will replace all hard-coded predefined types with intrinsic types, for similar reasons to the above.
2016-12-05 22:46:18 +01:00
bound[pname] = lit
}
}
for _, pname := range ast.StablePropertyBag(vals) {
if _, ok := props[pname]; !ok {
Introduce intrinsic types This change eliminates the special type mu/extension in favor of extensible intrinsic types. This subsumes the previous functionality while also fixing a number of warts with the old model. In particular, the old mu/extension approach deferred property binding until very late in the compiler. In fact, too late. The backend provider for an extension simply received an untyped bag of stuff, which it then had to deal with. Unfortunately, some operations in the binder are inaccessible at this point because doing so would cause a cycle. Furthermore, some pertinent information is gone at this point, like the scopes and symtables. The canonical example where we need this is binding services names to the services themselves; e.g., the AWS CloudFormation "DependsOn" property should resolve to the actual service names, not the string values. In the limit, this requires full binding information. There were a few solutions I considered, including ones that've required less code motion, however this one feels the most elegant. Now we permit types to be marked as "intrinsic." Binding to these names is done exactly as ordinary name binding, unlike the special mu/extension provider name. In fact, just about everything except code-generation for these types is the same as ordinary types. This is perfect for the use case at hand, which is binding properties. After this change, for example, "DependsOn" is expanded to real service names precisely as we need. As part of this change, I added support for three new basic schema types: * ast.StringList ("string[]"): a list of strings. * ast.StringMap ("map[string]any"): a map of strings to anys. * ast.ServiceList ("service[]"): a list of service references. Obviously we need to revisit this and add a more complete set. This work is already tracked by marapongo/mu#9. At the end of the day, it's likely I will replace all hard-coded predefined types with intrinsic types, for similar reasons to the above.
2016-12-05 22:46:18 +01:00
// TODO: edit distance checking to help with suggesting a fix.
p.Diag().Errorf(errors.ErrorUnrecognizedProperty.At(node), pname)
Introduce intrinsic types This change eliminates the special type mu/extension in favor of extensible intrinsic types. This subsumes the previous functionality while also fixing a number of warts with the old model. In particular, the old mu/extension approach deferred property binding until very late in the compiler. In fact, too late. The backend provider for an extension simply received an untyped bag of stuff, which it then had to deal with. Unfortunately, some operations in the binder are inaccessible at this point because doing so would cause a cycle. Furthermore, some pertinent information is gone at this point, like the scopes and symtables. The canonical example where we need this is binding services names to the services themselves; e.g., the AWS CloudFormation "DependsOn" property should resolve to the actual service names, not the string values. In the limit, this requires full binding information. There were a few solutions I considered, including ones that've required less code motion, however this one feels the most elegant. Now we permit types to be marked as "intrinsic." Binding to these names is done exactly as ordinary name binding, unlike the special mu/extension provider name. In fact, just about everything except code-generation for these types is the same as ordinary types. This is perfect for the use case at hand, which is binding properties. After this change, for example, "DependsOn" is expanded to real service names precisely as we need. As part of this change, I added support for three new basic schema types: * ast.StringList ("string[]"): a list of strings. * ast.StringMap ("map[string]any"): a map of strings to anys. * ast.ServiceList ("service[]"): a list of service references. Obviously we need to revisit this and add a more complete set. This work is already tracked by marapongo/mu#9. At the end of the day, it's likely I will replace all hard-coded predefined types with intrinsic types, for similar reasons to the above.
2016-12-05 22:46:18 +01:00
}
}
return bound
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
// bindValue takes a value and binds it to a type and literal AST node, returning nils if the conversions fails.
func (p *binderValidatePhase) bindValue(node *ast.Node, val interface{}, ty *ast.Type) ast.Literal {
contract.Assert(ty != nil)
var lit ast.Literal
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
if ty.IsDecors() {
lit = p.bindDecorsValue(node, val, ty.Decors)
} else if ty.IsPrimitive() {
lit = p.bindPrimitiveValue(node, val, *ty.Primitive)
} else if ty.IsStack() {
lit = p.bindServiceValue(node, val, ty)
} else if ty.IsSchema() {
lit = p.bindSchemaValue(node, val, ty.Schema)
} else if ty.IsUnresolvedRef() {
contract.FailM("Expected all unresolved refs to be gone by this phase in binding")
}
if lit == nil {
// If no successful type binding happened, issue an error.
p.Diag().Errorf(errors.ErrorIncorrectType.At(node), ty, reflect.TypeOf(val))
}
return lit
}
func (p *binderValidatePhase) bindDecorsValue(node *ast.Node, val interface{}, decors *ast.TypeDecors) ast.Literal {
// For decorated types, we need to recurse.
if decors.ElemType != nil {
arr := reflect.ValueOf(val)
if arr.Kind() == reflect.Slice {
len := arr.Len()
lits := make([]ast.Literal, len)
err := false
for i := 0; i < len; i++ {
v := arr.Index(i).Interface()
if lits[i] = p.bindValue(node, v, decors.ElemType); lits[i] == nil {
err = true
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
}
if !err {
return ast.NewArrayLiteral(node, decors.ElemType, lits)
}
} else {
glog.V(7).Infof("Expected array for value %v, got %v", val, arr.Kind())
}
} else {
contract.Assert(decors.KeyType != nil)
contract.Assert(decors.ValueType != nil)
// TODO: ensure that keytype is something we can actually use as a key (primitive).
m := reflect.ValueOf(val)
if m.Kind() == reflect.Map {
mk := m.MapKeys()
keys := make([]ast.Literal, len(mk))
err := false
for i := 0; i < len(mk); i++ {
k := mk[i].Interface()
if keys[i] = p.bindValue(node, k, decors.KeyType); keys[i] == nil {
glog.V(7).Infof("Error binding map key #%v (%v); expected %v",
i, k, decors.KeyType)
err = true
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
}
vals := make([]ast.Literal, len(mk))
for i := 0; i < len(mk); i++ {
v := m.MapIndex(mk[i])
if vals[i] = p.bindValue(node, v, decors.ValueType); vals[i] == nil {
glog.V(7).Infof("Error binding map value #%v (k=%v v=%v); expected %v",
i, mk[i].Interface(), v, decors.ValueType)
err = true
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
}
if !err {
return ast.NewMapLiteral(node, decors.KeyType, decors.ValueType, keys, vals)
}
} else {
glog.V(7).Infof("Expected map for value %v, got %v", val, m.Kind())
}
}
return nil
}
func (p *binderValidatePhase) bindPrimitiveValue(node *ast.Node, val interface{}, prim ast.PrimitiveType) ast.Literal {
// For primitive types, simply cast the target to the expected type.
switch prim {
case ast.PrimitiveTypeAny:
// Any is easy: just store it as-is.
return ast.NewAnyLiteral(node, val)
case ast.PrimitiveTypeString:
if s, ok := val.(string); ok {
return ast.NewStringLiteral(node, s)
}
return nil
case ast.PrimitiveTypeNumber:
if n, ok := val.(float64); ok {
return ast.NewNumberLiteral(node, n)
}
return nil
case ast.PrimitiveTypeBool:
if b, ok := val.(bool); ok {
return ast.NewBoolLiteral(node, b)
}
return nil
case ast.PrimitiveTypeService:
// Extract the name of the service reference as a string. Then bind it to an actual service in our symbol
// table, and store a strong reference to the result. This lets the backend connect the dots.
return p.bindServiceValue(node, val, nil)
default:
contract.FailMF("Unrecognized primitive type: %v", prim)
return nil
}
}
func (p *binderValidatePhase) bindServiceValue(node *ast.Node, val interface{}, expect *ast.Type) ast.Literal {
// Bind the capability ref for this stack type.
if s, ok := val.(string); ok {
if ref := p.bindServiceRef(node, s, expect); ref != nil {
return ast.NewServiceLiteral(node, ref)
}
}
return nil
}
func (p *binderValidatePhase) bindSchemaValue(node *ast.Node, val interface{}, schema *ast.Schema) ast.Literal {
// Bind the custom schema type. This is rather involved, but there are two primary cases:
// 1) A base type exists, plus an optional set of constraints on that base type (if it's a primitive).
// 2) A set of properties exist, meaning an entirely custom object. We must go recursive.
// TODO[marapongo/mu#9]: we may want to support mixing these (e.g., additive properties); for now, we won't.
if schema.BoundBase != nil {
// There is a base type. Bind it as-is, and then apply any additional constraints we have added.
contract.Assert(schema.Properties == nil)
lit := p.bindValue(node, val, schema.BoundBase)
if lit != nil {
// The following constraints are valid only on strings:
if schema.Pattern != nil {
if s, ok := conv.ToString(lit); ok {
rex := regexp.MustCompile(*schema.Pattern)
if rex.FindString(s) != s {
p.Diag().Errorf(errors.ErrorSchemaConstraintUnmet, "pattern", schema.Pattern, s)
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
} else {
p.Diag().Errorf(errors.ErrorSchemaConstraintType, "maxLength", ast.PrimitiveTypeString, lit.Type())
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
}
if schema.MaxLength != nil {
if s, ok := conv.ToString(lit); ok {
c := utf8.RuneCountInString(s)
if float64(c) > *schema.MaxLength {
p.Diag().Errorf(errors.ErrorSchemaConstraintUnmet,
"maxLength", fmt.Sprintf("max %v", schema.MaxLength), c)
}
} else {
p.Diag().Errorf(errors.ErrorSchemaConstraintType, "maxLength", ast.PrimitiveTypeString, lit.Type())
}
}
if schema.MinLength != nil {
if s, ok := conv.ToString(lit); ok {
c := utf8.RuneCountInString(s)
if float64(c) < *schema.MinLength {
p.Diag().Errorf(errors.ErrorSchemaConstraintUnmet,
"minLength", fmt.Sprintf("min %v", schema.MinLength), c)
}
} else {
p.Diag().Errorf(errors.ErrorSchemaConstraintType, "minLength", ast.PrimitiveTypeString, lit.Type())
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
// The following constraints are valid only on numeric ypes:
if schema.Maximum != nil {
if n, ok := conv.ToNumber(lit); ok {
if n > *schema.Maximum {
p.Diag().Errorf(errors.ErrorSchemaConstraintUnmet,
"maximum", fmt.Sprintf("max %v", schema.Maximum), n)
}
} else {
p.Diag().Errorf(errors.ErrorSchemaConstraintType, "maximum", ast.PrimitiveTypeNumber, lit.Type())
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
if schema.Minimum != nil {
if n, ok := conv.ToNumber(lit); ok {
if n < *schema.Minimum {
p.Diag().Errorf(errors.ErrorSchemaConstraintUnmet,
"minimum", fmt.Sprintf("min %v", schema.Minimum), n)
}
} else {
p.Diag().Errorf(errors.ErrorSchemaConstraintType, "minimum", ast.PrimitiveTypeNumber, lit.Type())
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
}
// The following constraints are valid on strings *and* number types.
if len(schema.Enum) > 0 {
if s, ok := conv.ToString(lit); ok {
ok := false
for _, e := range schema.Enum {
if s == e.(string) {
ok = true
break
}
}
if !ok {
p.Diag().Errorf(errors.ErrorSchemaConstraintUnmet,
"enum", fmt.Sprintf("enum %v", schema.Enum), s)
}
} else if n, ok := conv.ToNumber(lit); ok {
ok := false
for _, e := range schema.Enum {
if n == e.(float64) {
ok = true
break
}
}
if !ok {
p.Diag().Errorf(errors.ErrorSchemaConstraintUnmet,
"enum", fmt.Sprintf("enum %v", schema.Enum), n)
}
} else {
p.Diag().Errorf(errors.ErrorSchemaConstraintType,
"enum", ast.PrimitiveTypeString+" or "+ast.PrimitiveTypeNumber, lit.Type())
}
}
}
} else if schema.Properties != nil {
// There are some properties. This is a custom type. Bind the properties as usual.
if props, ok := val.(ast.PropertyBag); ok {
bag := p.bindProperties(node, schema.Properties, props)
return ast.NewSchemaLiteral(node, schema, bag)
}
}
Introduce intrinsic types This change eliminates the special type mu/extension in favor of extensible intrinsic types. This subsumes the previous functionality while also fixing a number of warts with the old model. In particular, the old mu/extension approach deferred property binding until very late in the compiler. In fact, too late. The backend provider for an extension simply received an untyped bag of stuff, which it then had to deal with. Unfortunately, some operations in the binder are inaccessible at this point because doing so would cause a cycle. Furthermore, some pertinent information is gone at this point, like the scopes and symtables. The canonical example where we need this is binding services names to the services themselves; e.g., the AWS CloudFormation "DependsOn" property should resolve to the actual service names, not the string values. In the limit, this requires full binding information. There were a few solutions I considered, including ones that've required less code motion, however this one feels the most elegant. Now we permit types to be marked as "intrinsic." Binding to these names is done exactly as ordinary name binding, unlike the special mu/extension provider name. In fact, just about everything except code-generation for these types is the same as ordinary types. This is perfect for the use case at hand, which is binding properties. After this change, for example, "DependsOn" is expanded to real service names precisely as we need. As part of this change, I added support for three new basic schema types: * ast.StringList ("string[]"): a list of strings. * ast.StringMap ("map[string]any"): a map of strings to anys. * ast.ServiceList ("service[]"): a list of service references. Obviously we need to revisit this and add a more complete set. This work is already tracked by marapongo/mu#9. At the end of the day, it's likely I will replace all hard-coded predefined types with intrinsic types, for similar reasons to the above.
2016-12-05 22:46:18 +01:00
return nil
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
// bindServiceRef binds a string to a service reference, resulting in a ServiceRef. The reference is expected
Introduce intrinsic types This change eliminates the special type mu/extension in favor of extensible intrinsic types. This subsumes the previous functionality while also fixing a number of warts with the old model. In particular, the old mu/extension approach deferred property binding until very late in the compiler. In fact, too late. The backend provider for an extension simply received an untyped bag of stuff, which it then had to deal with. Unfortunately, some operations in the binder are inaccessible at this point because doing so would cause a cycle. Furthermore, some pertinent information is gone at this point, like the scopes and symtables. The canonical example where we need this is binding services names to the services themselves; e.g., the AWS CloudFormation "DependsOn" property should resolve to the actual service names, not the string values. In the limit, this requires full binding information. There were a few solutions I considered, including ones that've required less code motion, however this one feels the most elegant. Now we permit types to be marked as "intrinsic." Binding to these names is done exactly as ordinary name binding, unlike the special mu/extension provider name. In fact, just about everything except code-generation for these types is the same as ordinary types. This is perfect for the use case at hand, which is binding properties. After this change, for example, "DependsOn" is expanded to real service names precisely as we need. As part of this change, I added support for three new basic schema types: * ast.StringList ("string[]"): a list of strings. * ast.StringMap ("map[string]any"): a map of strings to anys. * ast.ServiceList ("service[]"): a list of service references. Obviously we need to revisit this and add a more complete set. This work is already tracked by marapongo/mu#9. At the end of the day, it's likely I will replace all hard-coded predefined types with intrinsic types, for similar reasons to the above.
2016-12-05 22:46:18 +01:00
// to be in the form "<service>[:<selector>]", where <service> is the name of a service that's currently in scope, and
// <selector> is an optional selector of a public service exported from that service.
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
func (p *binderValidatePhase) bindServiceRef(node *ast.Node, val string, ty *ast.Type) *ast.ServiceRef {
glog.V(5).Infof("Binding capref '%v'", val)
// Peel off the selector, if there is one.
var sels string
if selix := strings.LastIndex(val, ":"); selix != -1 {
sels = val[selix+1:]
val = val[:selix]
}
// Validate and convert the name and selector to names.
var nm ast.Name
if ast.IsName(val) {
nm = ast.AsName(val)
} else {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorNotAName.At(node), val)
}
var sel ast.Name
if sels != "" {
if ast.IsName(sels) {
sel = ast.AsName(sels)
} else {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorNotAName.At(node), sels)
}
}
// If we have errors at this juncture, bail early, before it just gets worse.
if !p.Diag().Success() {
return nil
}
// Bind the name to a service.
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
var ref *ast.ServiceRef
if svc, ok := p.b.LookupService(ast.Name(nm)); ok {
svct := svc.BoundType
contract.AssertMF(svct != nil, "Expected service '%v' to have a type", svc.Name)
var selsvc *ast.Service
if sel == "" {
// If no selector was specified, just use the service itself as the selsvc.
selsvc = svc
} else if sel == "." {
// A special dot selector can be used to pick the sole public service.
if len(svct.Services.Public) == 0 {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorServiceHasNoPublics.At(node), svc.Name, svct.Name)
} else if len(svct.Services.Public) == 1 {
for _, pub := range svct.Services.Public {
selsvc = pub
break
}
} else {
contract.Assert(len(svct.Services.Public) > 1)
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorServiceHasManyPublics.At(node), svc.Name, svct.Name)
}
} else {
// If a selector was specified, ensure that it actually exists.
if entry, ok := svct.Services.Public[sel]; ok {
selsvc = entry
} else {
// The selector wasn't found. Issue an error. If there's a private service by that name,
// say so, for better diagnostics.
if _, has := svct.Services.Private[sel]; has {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorServiceSelectorIsPrivate.At(node), sel, svc.Name, svct.Name)
} else {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorServiceSelectorNotFound.At(node), sel, svc.Name, svct.Name)
}
}
}
if selsvc != nil {
// If there is an expected type, now ensure that the selected Service is of the right kind.
contract.Assert(selsvc.BoundType != nil)
if ty != nil && !subclassOf(selsvc.BoundType, ty) {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorIncorrectType.At(node), ty, selsvc.BoundType.Name)
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
ref = &ast.ServiceRef{
Name: nm,
Selector: sel,
Service: svc,
Selected: selsvc,
}
}
} else {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
p.Diag().Errorf(errors.ErrorServiceNotFound.At(node), nm)
}
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
return ref
}
// subclassOf checks that the left type ("typ") is equal to or a subclass of the right type ("or"). The right type is a
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
// union between *ast.Stack and *ast.UninstStack, so that it can be an uninstantiated type if needed.
func subclassOf(typ *ast.Stack, of *ast.Type) bool {
for typ != nil {
if typ == of.Stack {
// If the type matches the target directly, obviously it's a hit.
return true
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
} else if of.UninstStack != nil {
// If the type was produced from the same "document" (uninstantiated type), then it's also a hit. Note that
// due to template expansion, we need to walk the document hierarchy to see if there's a match.
doc := typ.Doc
for doc != nil {
Custom types, round 1 This change overhauls the core of how types are used by the entire compiler. In particular, we now have an ast.Type, and have begun using its use where appropriate. An ast.Type is a union representing precisely one of the possible sources of types in the system: * Primitive type: any, bool, number, string, or service. * Stack type: a resolved reference to an actual concrete stack. * Schema type: a resolved reference to an actual concrete schema. * Unresolved reference: a textual reference that hasn't yet been resolved to a concrete artifact. * Uninstantiated reference: a reference that has been resolved to an uninstantiated stack, but hasn't been bound to a concrete result yet. Right now, this can point to a stack, however eventually we would imagine this supporting inter-stack schema references also. * Decorated type: either an array or a map; in the array case, there is a single inner element type; in the map case, there are two, the keys and values; in all cases, the type recurses to any of the possibilities listed here. All of the relevant AST nodes have been overhauled accordingly. In addition to this, we now have an ast.Schema type. It is loosely modeled on JSON Schema in its capabilities (http://json-schema.org/). Although we parse and perform some visitation and binding of these, there are mostly placeholders left in the code for the interesting aspects, such as registering symbols, resolving dependencies, and typechecking usage of schema types. This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
if doc == of.UninstStack.Doc {
return true
}
doc = doc.Parent
}
}
// Finally, if neither of those worked, we must see if there's a base class and keep searching.
typ = typ.BoundBase
}
return false
}
// refToName converts a reference to its simple symbolic name.
func refToName(ref ast.Ref) ast.Name {
return ref.MustParse().Name
}