blob: 85e3b39838d88442b8e548eff0bd305240098a4a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package compile
import (
"v.io/v23/vdl"
"v.io/x/lib/toposort"
"v.io/x/ref/lib/vdl/parse"
)
// compileInterfaces is the "entry point" to the rest of this file. It takes
// the interfaces defined in pfiles and compiles them into Interfaces in pkg.
func compileInterfaces(pkg *Package, pfiles []*parse.File, env *Env) {
id := ifaceDefiner{pkg, pfiles, env, make(map[string]*ifaceBuilder)}
if id.Declare(); !env.Errors.IsEmpty() {
return
}
id.Define()
}
// ifaceDefiner defines interfaces in a package. This is split into two phases:
// 1) Declare ensures local interface references can be resolved.
// 2) Define sorts in dependency order, and defines each interface.
//
// It holds a builders map from interface name to ifaceBuilder, where the
// ifaceBuilder is responsible for compiling and defining a single interface.
type ifaceDefiner struct {
pkg *Package
pfiles []*parse.File
env *Env
builders map[string]*ifaceBuilder
}
type ifaceBuilder struct {
def *Interface
pdef *parse.Interface
}
func printIfaceBuilderName(ibuilder interface{}) string {
return ibuilder.(*ifaceBuilder).def.Name
}
// Declare creates builders for each interface defined in the package.
func (id ifaceDefiner) Declare() {
for ix := range id.pkg.Files {
file, pfile := id.pkg.Files[ix], id.pfiles[ix]
for _, pdef := range pfile.Interfaces {
export, err := validIdent(pdef.Name, reservedNormal)
if err != nil {
id.env.prefixErrorf(file, pdef.Pos, err, "interface %s invalid name", pdef.Name)
continue // keep going to catch more errors
}
detail := identDetail("interface", file, pdef.Pos)
if err := file.DeclareIdent(pdef.Name, detail); err != nil {
id.env.prefixErrorf(file, pdef.Pos, err, "interface %s name conflict", pdef.Name)
continue
}
def := &Interface{NamePos: NamePos(pdef.NamePos), Exported: export, File: file}
id.builders[pdef.Name] = &ifaceBuilder{def, pdef}
}
}
}
// Define interfaces. We sort by dependencies on other interfaces in this
// package. The sorting is to ensure there are no cycles.
func (id ifaceDefiner) Define() {
// Populate sorter with dependency information. The sorting ensures that the
// list of interfaces within each file is topologically sorted, and also
// deterministic; in the absence of interface embeddings, interfaces are
// listed in the same order they were defined in the parsed files.
var sorter toposort.Sorter
for _, pfile := range id.pfiles {
for _, pdef := range pfile.Interfaces {
b := id.builders[pdef.Name]
sorter.AddNode(b)
for _, dep := range id.getLocalDeps(b) {
sorter.AddEdge(b, dep)
}
}
}
// Sort and check for cycles.
sorted, cycles := sorter.Sort()
if len(cycles) > 0 {
cycleStr := toposort.DumpCycles(cycles, printIfaceBuilderName)
first := cycles[0][0].(*ifaceBuilder)
id.env.Errorf(first.def.File, first.def.Pos, "package %v has cyclic interfaces: %v", id.pkg.Name, cycleStr)
return
}
// Define all interfaces. Since we add the interfaces as we go and evaluate
// in topological order, dependencies are guaranteed to be resolvable when we
// get around to defining the interfaces that embed on them.
for _, ibuilder := range sorted {
b := ibuilder.(*ifaceBuilder)
id.define(b)
addIfaceDef(b.def)
}
}
// addIfaceDef updates our various structures to add a new interface.
func addIfaceDef(def *Interface) {
def.File.Interfaces = append(def.File.Interfaces, def)
def.File.Package.ifaceDefs = append(def.File.Package.ifaceDefs, def)
def.File.Package.ifaceMap[def.Name] = def
}
// getLocalDeps returns the list of interface dependencies for b that are in
// this package.
func (id ifaceDefiner) getLocalDeps(b *ifaceBuilder) (deps []*ifaceBuilder) {
for _, pe := range b.pdef.Embeds {
// Embeddings of other interfaces in this package are all we care about.
if dep := id.builders[pe.Name]; dep != nil {
deps = append(deps, dep)
}
}
return
}
func (id ifaceDefiner) define(b *ifaceBuilder) {
// Methods must be defined before embeddings, so that we can check whether any
// of the embeddings add duplicate methods.
methods := id.defineMethods(b)
id.defineEmbeds(b, methods)
}
func (id ifaceDefiner) defineMethods(b *ifaceBuilder) map[string]*Method {
def, file := b.def, b.def.File
defined := make(map[string]*Method)
// Now validate and define each method.
for _, pm := range b.pdef.Methods {
if err := validExportedIdent(pm.Name, reservedFirstRuneLower); err != nil {
id.env.Errorf(file, pm.Pos, "method %s name (%s)", pm.Name, err)
continue // keep going to catch more errors
}
if dup := defined[pm.Name]; dup != nil {
id.env.Errorf(file, pm.Pos, "method %s redefined (previous at %s)", pm.Name, dup.Pos)
continue // keep going to catch more errors
}
m := &Method{NamePos: NamePos(pm.NamePos), Interface: def}
m.InArgs = id.defineArgs(in, m.NamePos, pm.InArgs, file)
m.OutArgs = id.defineArgs(out, m.NamePos, pm.OutArgs, file)
m.InStream = id.defineStreamType(pm.InStream, file)
m.OutStream = id.defineStreamType(pm.OutStream, file)
m.Tags = id.defineTags(pm.Tags, file)
def.Methods = append(def.Methods, m)
defined[pm.Name] = m
}
return defined
}
func (id ifaceDefiner) defineEmbeds(b *ifaceBuilder, methods map[string]*Method) {
def, file := b.def, b.def.File
seen := make(map[string]*parse.NamePos)
for _, pe := range b.pdef.Embeds {
if dup := seen[pe.Name]; dup != nil {
id.env.Errorf(file, pe.Pos, "interface %s duplicate embedding (previous at %s)", pe.Name, dup.Pos)
continue // keep going to catch more errors
}
seen[pe.Name] = pe
// Resolve the embedded interface.
embed, matched := id.env.ResolveInterface(pe.Name, file)
if embed == nil {
id.env.Errorf(file, pe.Pos, "interface %s undefined", pe.Name)
continue // keep going to catch more errors
}
if len(matched) < len(pe.Name) {
id.env.Errorf(file, pe.Pos, "interface %s invalid (%s unmatched)", pe.Name, pe.Name[len(matched):])
continue // keep going to catch more errors
}
// Check for method redefinition in the embeddings.
//
// TODO(toddw): We may relax this rule in the future, to allow duplicate
// methods with identical signatures.
for _, m := range embed.AllMethods() {
if dup := methods[m.Name]; dup != nil {
pos1, pos2 := m.Pos.String(), dup.Pos.String()
if m.Interface != def {
pos1 = fpString(m.Interface.File, m.Pos)
}
if dup.Interface != def {
pos2 = fpString(dup.Interface.File, dup.Pos)
}
id.env.Errorf(file, pe.Pos, "embedded method %s redefined (defined at %s and %s)", m.Name, pos1, pos2)
}
methods[m.Name] = m
}
def.Embeds = append(def.Embeds, embed)
}
}
type inout string
const (
in inout = "in"
out inout = "out"
)
func (id ifaceDefiner) defineArgs(io inout, method NamePos, pargs []*parse.Field, file *File) (args []*Field) {
seen := make(map[string]*parse.Field)
for _, parg := range pargs {
if dup := seen[parg.Name]; dup != nil && parg.Name != "" {
id.env.Errorf(file, parg.Pos, "method %s arg %s duplicate name (previous at %s)", method.Name, parg.Name, dup.Pos)
continue // keep going to catch more errors
}
seen[parg.Name] = parg
switch {
case io == in && parg.Name == "":
id.env.Errorf(file, parg.Pos, "method %s in-arg unnamed (must name all in-args)", method.Name)
continue // keep going to catch more errors
case io == out && len(pargs) > 1 && parg.Name == "":
id.env.Errorf(file, parg.Pos, "method %s out-arg unnamed (must name all out-args if there are more than 1)", method.Name)
continue // keep going to catch more errors
}
if parg.Name != "" {
if _, err := validIdent(parg.Name, reservedFirstRuneLower); err != nil {
id.env.prefixErrorf(file, parg.Pos, err, "method %s invalid arg %s", method.Name, parg.Name)
continue // keep going to catch more errors
}
}
arg := &Field{NamePos(parg.NamePos), compileExportedType(parg.Type, file, id.env)}
args = append(args, arg)
}
return
}
func (id ifaceDefiner) defineStreamType(ptype parse.Type, file *File) *vdl.Type {
if ptype == nil {
return nil
}
if tn, ok := ptype.(*parse.TypeNamed); ok && tn.Name == "_" {
// Special-case the _ placeholder, which means there's no stream type.
return nil
}
return compileExportedType(ptype, file, id.env)
}
func (id ifaceDefiner) defineTags(ptags []parse.ConstExpr, file *File) (tags []*vdl.Value) {
// TODO(toddw): Should we require that tag types are transitively exported?
for _, ptag := range ptags {
if tag := compileConst("tag", nil, ptag, file, id.env); tag != nil {
tags = append(tags, tag)
}
}
return
}