blob: d5a2642180f7f750b832c9d095b42fb9da7b3ac1 [file] [log] [blame] [edit]
// Copyright 2022 The Fuchsia 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 fidlgen_cpp
import (
"fmt"
"sort"
"strings"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
// DeclDepGraph represents the definitional dependency graph between
// declarations in a library.
//
// We do not create edges to nullable types. This makes it possible to write
// some recursive types in FIDL. However, we do not have comprehensive support
// for recursive types. See https://fxbug.dev/42110612 for details.
//
// Similarly, we do not create edges to protocols via endpoint dependencies, as
// that same protocol may freely appear in the response type of one its
// methods.
type DeclDepGraph struct {
nodes declDepNodeMap
decls map[fidlgen.EncodedCompoundIdentifier]fidlgen.Decl
}
type declDepNode struct {
// The name of the associated declaration.
name fidlgen.EncodedCompoundIdentifier
// The reverse dependencies (i.e., dependents) of the associated
// declaration. This is tracked to facilitate a topological sort with
// respect to dependency.
revDeps declDepNodeMap
}
type declDepNodeMap map[fidlgen.EncodedCompoundIdentifier]*declDepNode
// NewDeclDepGraph computes the dependency graph for a given library.
func NewDeclDepGraph(r fidlgen.Root) DeclDepGraph {
g := DeclDepGraph{
nodes: make(declDepNodeMap),
decls: make(map[fidlgen.EncodedCompoundIdentifier]fidlgen.Decl),
}
r.ForEachDecl(func(decl fidlgen.Decl) { g.addDecl(decl) })
return g
}
// SortedDecls returns a deterministic, topologically sorted list of the
// associated declarations, so that a declaration always appears before those
// that depend on it. Beyond preserving this relation, the list attempts to
// prioritize declarations in terms of their original ordering in source.
func (g *DeclDepGraph) SortedDecls() []fidlgen.Decl {
const (
nodeProccesing int = iota
nodeProcessed
)
var decls []fidlgen.Decl
nodeState := make(map[fidlgen.EncodedCompoundIdentifier]int)
var visit func(*declDepNode, []string)
visit = func(node *declDepNode, depChain []string) {
depChain = append(depChain, string(node.name))
if state, ok := nodeState[node.name]; ok {
switch state {
case nodeProccesing:
panic(fmt.Sprintf("cyclic dependency: %s", strings.Join(depChain, " <- ")))
case nodeProcessed:
return
}
}
nodeState[node.name] = nodeProccesing
for _, dep := range g.normalizeNodes(node.revDeps) {
visit(dep, depChain)
}
if decl, ok := g.decls[node.name]; ok {
decls = append([]fidlgen.Decl{decl}, decls...)
}
nodeState[node.name] = nodeProcessed
depChain = depChain[:len(depChain)-1]
}
for _, node := range g.normalizeNodes(g.nodes) {
visit(node, nil)
}
return decls
}
// GetDirectDependents returns the declarations that are directly dependent on
// a given one, referenced by name. The returned declarations are given in
// source order (lexicographically first on filename). A boolean is also
// returned indicating whether the provided declaration is contained in the
// graph.
func (g DeclDepGraph) GetDirectDependents(name fidlgen.EncodedCompoundIdentifier) ([]fidlgen.Decl, bool) {
// First check whether the provided name represents a local declaration.
if _, ok := g.decls[name]; !ok {
return nil, false
}
node, ok := g.nodes[name]
if !ok {
return nil, false
}
var decls []fidlgen.Decl
for _, revDep := range node.revDeps {
// All direct dependents on a local declaration should be local declarations themselves.
decls = append(decls, g.decls[revDep.name])
}
sort.Slice(decls, func(i, j int) bool {
return fidlgen.LocationCmp(decls[i].GetLocation(), decls[j].GetLocation())
})
return decls, true
}
// Since `map` access is randomized, a normalization of `declDepNodeMap`s is
// needed to produce a deterministic list. Topological sorting alone is
// insufficient, as there are many possible orderings that preserve that
// relation.
//
// To approximate the desired order described on SortedDecls(), we sort nodes
// with regards to the *opposite* of the following relations (opposite since we
// will be *prepending* items in this order during topological sorting):
//
// * If both nodes represent local (i.e., non-imported) declarations, source
// ordering is preserved (falling back to a lexicographic comparison on
// filenames if the two declarations came from different files).
//
// * If both nodes represent imported declarations, the one with the
// lexicographically smaller name is prioritized. (This is an arbitrary choice.)
//
// * If only one node of the two represents a local declaration, the local one
// is prioritized. (This is also an arbitrary choice.)
func (g DeclDepGraph) normalizeNodes(m declDepNodeMap) []*declDepNode {
var nodes []*declDepNode
for _, node := range m {
nodes = append(nodes, node)
}
sort.Slice(nodes, func(i, j int) bool {
iName := nodes[i].name
jName := nodes[j].name
iDecl, iLocal := g.decls[iName]
jDecl, jLocal := g.decls[jName]
if iLocal && jLocal {
return !fidlgen.LocationCmp(iDecl.GetLocation(), jDecl.GetLocation())
}
if iLocal != jLocal {
return iLocal
}
return strings.Compare(string(iName), string(jName)) > 0
})
return nodes
}
func (g *DeclDepGraph) addDecl(decl fidlgen.Decl) {
node := g.getNode(decl.GetName())
g.decls[node.name] = decl
switch decl := decl.(type) {
case *fidlgen.Const:
g.addDepsFromType(node, decl.Type)
g.addDepsFromConstant(node, decl.Value)
case *fidlgen.Bits:
g.addDepsFromType(node, decl.Type)
for _, m := range decl.Members {
g.addDepsFromConstant(node, m.Value)
}
case *fidlgen.Enum:
for _, m := range decl.Members {
g.addDepsFromConstant(node, m.Value)
}
case *fidlgen.Resource:
for _, prop := range decl.Properties {
g.addDepsFromType(node, prop.Type)
}
case *fidlgen.Protocol:
for _, comp := range decl.Composed {
g.addDep(node, comp.Name)
}
for _, m := range decl.Methods {
if req, ok := m.GetRequestPayloadIdentifier(); ok {
g.addDep(node, req)
}
if resp, ok := m.GetResponsePayloadIdentifier(); ok {
g.addDep(node, resp)
}
}
case *fidlgen.Service:
for _, m := range decl.Members {
g.addDepsFromType(node, m.Type)
}
case *fidlgen.Struct:
for _, m := range decl.Members {
g.addDepsFromType(node, m.Type)
if m.MaybeDefaultValue != nil {
g.addDepsFromConstant(node, *m.MaybeDefaultValue)
}
if m.MaybeFromAlias != nil {
g.addDepsFromTypeCtor(node, *m.MaybeFromAlias)
}
}
case *fidlgen.Table:
for _, m := range decl.Members {
g.addDepsFromType(node, m.Type)
if m.MaybeFromAlias != nil {
g.addDepsFromTypeCtor(node, *m.MaybeFromAlias)
}
}
case *fidlgen.Union:
for _, m := range decl.Members {
g.addDepsFromType(node, m.Type)
if m.MaybeFromAlias != nil {
g.addDepsFromTypeCtor(node, *m.MaybeFromAlias)
}
}
case *fidlgen.Alias:
g.addDepsFromTypeCtor(node, decl.PartialTypeConstructor)
case *fidlgen.NewType:
if decl.Alias != nil {
g.addDepsFromTypeCtor(node, *decl.Alias)
} else {
g.addDepsFromType(node, decl.Type)
}
case *fidlgen.Overlay:
for _, m := range decl.Members {
g.addDepsFromType(node, m.Type)
if m.MaybeFromAlias != nil {
g.addDepsFromTypeCtor(node, *m.MaybeFromAlias)
}
}
}
}
func (g *DeclDepGraph) getNode(decl fidlgen.EncodedCompoundIdentifier) *declDepNode {
node, ok := g.nodes[decl]
if !ok {
node = &declDepNode{
name: decl,
revDeps: make(declDepNodeMap),
}
g.nodes[decl] = node
}
return node
}
func (g *DeclDepGraph) addDep(node *declDepNode, dep fidlgen.EncodedCompoundIdentifier) {
g.getNode(dep).revDeps[node.name] = node
}
func (g *DeclDepGraph) addDepsFromConstant(node *declDepNode, c fidlgen.Constant) {
if c.Kind == fidlgen.IdentifierConstant {
g.addDep(node, c.Identifier)
}
}
func (g *DeclDepGraph) addDepsFromType(node *declDepNode, typ fidlgen.Type) {
// As above, we do not create edges to nullable types or protocols via
// endpoint dependencies.
if typ.Nullable || typ.Kind == fidlgen.EndpointType {
return
}
switch typ.Kind {
case fidlgen.ArrayType, fidlgen.VectorType:
g.addDepsFromType(node, *typ.ElementType)
case fidlgen.HandleType:
// TODO(https://fxbug.dev/42156522): ResourceIdentifier should be an
// `fidlgen.EncodedCompoundIdentifier`.
g.addDep(node, fidlgen.EncodedCompoundIdentifier(typ.ResourceIdentifier))
case fidlgen.IdentifierType:
g.addDep(node, typ.Identifier)
}
}
func (g *DeclDepGraph) addDepsFromTypeCtor(node *declDepNode, ctor fidlgen.PartialTypeConstructor) {
// As above, we do not create edges to nullable types.
if ctor.Nullable {
return
}
if !ctor.Name.IsBuiltIn() {
g.addDep(node, ctor.Name)
}
for _, arg := range ctor.Args {
g.addDepsFromTypeCtor(node, arg)
}
}