Skip to content

Commit

Permalink
Move generic package/type/value analysis code to ssax.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmalloc committed Nov 1, 2024
1 parent 14c0a18 commit b279e59
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 80 deletions.
7 changes: 6 additions & 1 deletion config/staticconfig/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"slices"

"github.com/dogmatiq/enginekit/config"
"github.com/dogmatiq/enginekit/config/staticconfig/internal/ssax"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
Expand Down Expand Up @@ -123,9 +124,13 @@ func Analyze(pkgs []*packages.Package) Analysis {
continue
}

// Search through all members of the package to find types that
// implement [dogma.Application].
for _, m := range pkg.Members {
if t, ok := m.(*ssa.Type); ok {
analyzeType(ctx, t.Type())
if r, ok := ssax.Implements(t, ctx.Dogma.Application); ok {
analyzeApplicationType(ctx, r)
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion config/staticconfig/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"go/types"

"github.com/dogmatiq/enginekit/config/staticconfig/internal/ssax"
"golang.org/x/tools/go/ssa"
)

Expand Down Expand Up @@ -64,7 +65,7 @@ func resolveDogmaPackage(ctx *context) bool {
}

func (c *context) LookupMethod(t types.Type, name string) *ssa.Function {
fn := c.Program.LookupMethod(t, packageOf(t), name)
fn := c.Program.LookupMethod(t, ssax.Package(t), name)
if fn == nil {
panic(fmt.Sprintf("method not found: %s.%s", t, name))
}
Expand Down
9 changes: 5 additions & 4 deletions config/staticconfig/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package staticconfig
import (
"github.com/dogmatiq/enginekit/config"
"github.com/dogmatiq/enginekit/config/internal/configbuilder"
"golang.org/x/tools/go/ssa"
"github.com/dogmatiq/enginekit/config/staticconfig/internal/ssax"
)

func analyzeHandler[T configbuilder.HandlerBuilder](
Expand All @@ -14,15 +14,16 @@ func analyzeHandler[T configbuilder.HandlerBuilder](
build(func(b T) {
b.UpdateFidelity(ctx.Fidelity)

inst, ok := ctx.Args[0].(*ssa.MakeInterface)
if !ok {
t := ssax.ConcreteType(ctx.Args[0])

if !t.IsPresent() {
b.UpdateFidelity(config.Incomplete)
return
}

analyzeEntity(
ctx.context,
inst.X.Type(),
t.Get(),
b,
func(ctx *configurerCallContext[T]) {
switch ctx.Method.Name() {
Expand Down
122 changes: 122 additions & 0 deletions config/staticconfig/internal/ssax/type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package ssax

import (
"fmt"
"go/types"

"github.com/dogmatiq/enginekit/internal/typename"
"github.com/dogmatiq/enginekit/optional"
"golang.org/x/tools/go/ssa"
)

// Implements reports whether t implements i, regardless of whether it
// uses pointer or non-pointer method receivers.
//
// If ok is true, r is the receiver type that implements the interface, which
// may be either t or *t.
func Implements(t *ssa.Type, i *types.Interface) (r types.Type, ok bool) {
r = t.Type()

if IsAbstract(r) {
return nil, false
}

if types.Implements(r, i) {
return r, true
}

r = types.NewPointer(r)
if types.Implements(r, i) {
return r, true
}

return nil, false
}

// IsAbstract returns true if t is abstract, either because it refers to an
// interface or because it is a generic type that has not been instantiated.
func IsAbstract(t types.Type) bool {
if types.IsInterface(t) {
return true
}

// Check if the type is a generic type that has not been instantiated
// (meaning that it has no concrete values for its type parameters).
switch t := t.(type) {
case *types.Named:
return t.Origin() == t && t.TypeParams().Len() != 0
case *types.Alias:
return t.Origin() == t && t.TypeParams().Len() != 0
}

return false
}

// Package returns the package in which the elemental type of t is declared.
func Package(t types.Type) *types.Package {
switch t := t.(type) {
case *types.Named:
return t.Obj().Pkg()
case *types.Alias:
return t.Obj().Pkg()
case *types.Pointer:
return Package(t.Elem())
default:
panic(fmt.Sprintf("cannot determine package for anonymous or built-in type %v", t))
}
}

// ConcreteType returns the concrete type of v, if it can be determined at
// compile-time.
func ConcreteType(v ssa.Value) optional.Optional[types.Type] {
t := v.Type()

if !IsAbstract(t) {
return optional.Some(t)
}

switch v := v.(type) {
case *ssa.Alloc:
case *ssa.BinOp:
case *ssa.Builtin:
case *ssa.Call:
case *ssa.ChangeInterface:
case *ssa.ChangeType:
case *ssa.Const:
// We made it past the IsAbstract() check so we know this is a constant
// nil value for an interface, and hence no type information is present.
return optional.None[types.Type]()
case *ssa.Convert:
case *ssa.Extract:
case *ssa.Field:
case *ssa.FieldAddr:
case *ssa.FreeVar:
case *ssa.Function:
case *ssa.Global:
case *ssa.Index:
case *ssa.IndexAddr:
case *ssa.Lookup:
case *ssa.MakeChan:
case *ssa.MakeClosure:
case *ssa.MakeInterface:
return ConcreteType(v.X)
case *ssa.MakeMap:
case *ssa.MakeSlice:
case *ssa.MultiConvert:
case *ssa.Next:
case *ssa.Parameter:
case *ssa.Phi:
case *ssa.Slice:
case *ssa.SliceToArrayPointer:
case *ssa.TypeAssert:
case *ssa.UnOp:
_ = v

case *ssa.Range, *ssa.Select:
// These types implement ssa.Value, but they can not actually be used as
// expressions in Go.
return optional.None[types.Type]()
}

panic(fmt.Sprintf("unhandled %T of type %s", v, typename.OfStatic(t)))
}
74 changes: 0 additions & 74 deletions config/staticconfig/type.go

This file was deleted.

0 comments on commit b279e59

Please sign in to comment.