Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

730 lines
24 KiB
Go

package resolver
import (
"strings"
"github.com/zzet/gortex/internal/graph"
)
// ResolveRustScopeCalls is the graph-wide materialisation pass for the
// Rust-specific scope layer. It lands Rust call edges the generic
// resolver leaves unresolved by applying Rust's own scoping rules:
//
// 1. impl-block method owner. `Foo::new()` resolves to the `new`
// method defined in `impl Foo { fn new(...) }`. The Rust extractor
// stamps the full scoped path on the call edge as Meta["rust_path"]
// ("Foo::new"); this pass reads the qualifier ("Foo") and binds the
// trailing segment to a method whose owner type (Node.Meta
// ["receiver"]) is that qualifier. Resolved at ast_resolved — the
// receiver type is named in source, so the binding is structurally
// unambiguous within the qualifier's type.
//
// 2. self / Self receiver. Inside `impl Foo`, `self.bar()` and
// `Self::new()` resolve to Foo's methods. The caller is a Rust
// method node carrying Meta["receiver"]="Foo", so the enclosing
// impl type is read off the caller and the call binds to a method
// of that owner. Resolved at ast_resolved.
//
// 3. module-path. `crate::module::func()`, `super::func()`,
// `self::func()` and `module::func()` resolve to a free function
// named by the path's trailing segment. Gortex does not model the
// Rust module tree as graph nodes, so the binding matches the
// trailing segment against free functions in the caller's repo,
// preferring a same-file then same-directory candidate. Resolved at
// ast_inferred — the module prefix is not verified against a real
// module node, only the trailing name and locality.
//
// Local-shadows-import precedence: before binding a module-path call to
// a free function, the pass checks whether the caller has a parameter
// of the same name (a local binding that, in Rust, shadows an imported
// item of the same identifier). When it does, the call is left
// unresolved rather than bound to the (shadowed) import target.
//
// The pass only ever rewrites an edge whose target is still an
// `unresolved::` placeholder, so it never fights or overrides a binding
// the generic resolver already landed; it strictly fills in the
// residual the generic pass missed. It is a full recompute and
// idempotent — each candidate edge's target is recomputed from its own
// Meta on every run, so a reindex of either endpoint's file leaves the
// edge's resolution stable. graph.ReindexEdges keeps the out/in buckets
// consistent.
//
// Ambiguity is resolved conservatively: when more than one candidate
// matches, the pass skips the edge (zero false positives over breadth).
//
// Out of scope (left for the generic resolver, the cross-repo resolver,
// or future work): cross-repo Rust calls, trait-bound / generic-typed
// receivers, fully-qualified `<T as Trait>::method` UFCS, and resolving
// a module path against a real module-tree node (only the trailing
// segment + locality is used today).
//
// Returns the number of Rust call edges this pass landed on a concrete
// node.
func ResolveRustScopeCalls(g graph.Store) int {
if g == nil {
return 0
}
// Module-path `use crate::…`/`self::…`/`super::…` → module-file binding.
// Independent of the call-edge resolution below, so it runs even when the
// graph has no unresolved Rust call edges.
bound := resolveRustModuleImports(g)
idx := buildRustScopeIndex(g)
if idx == nil {
return bound
}
// Trait-impl override edges bind independently of unresolved call edges,
// so resolve them before the call-edge early-out below.
resolved := resolveRustTraitOverrides(g, idx)
var reindexBatch []graph.EdgeReindex
// Collect candidate edges (still-unresolved Rust EdgeCalls) plus the
// caller IDs we need to read receiver type / repo / params off, so
// the per-edge node lookups collapse to one batch.
type candEdge struct {
edge *graph.Edge
}
var cands []candEdge
fromIDs := make(map[string]struct{})
for e := range g.EdgesByKind(graph.EdgeCalls) {
if e == nil {
continue
}
if !graph.IsUnresolvedTarget(e.To) {
continue
}
if !rustScopeEdgeCandidate(e) {
continue
}
cands = append(cands, candEdge{edge: e})
if e.From != "" {
fromIDs[e.From] = struct{}{}
}
}
if len(cands) == 0 {
return bound + resolved
}
fromList := make([]string, 0, len(fromIDs))
for id := range fromIDs {
fromList = append(fromList, id)
}
callerNodes := g.GetNodesByIDs(fromList)
for _, c := range cands {
e := c.edge
caller := callerNodes[e.From]
if caller == nil || caller.Language != "rust" {
continue
}
targetID := idx.resolve(e, caller)
if targetID == "" || targetID == e.To {
continue
}
oldTo := e.To
e.To = targetID
e.Origin = idx.lastOrigin
e.Confidence = idx.lastConfidence
e.ConfidenceLabel = graph.ConfidenceLabelFor(graph.EdgeCalls, idx.lastConfidence)
if e.Meta == nil {
e.Meta = map[string]any{}
}
e.Meta["rust_resolution"] = idx.lastReason
reindexBatch = append(reindexBatch, graph.EdgeReindex{Edge: e, OldTo: oldTo})
resolved++
}
if len(reindexBatch) > 0 {
g.ReindexEdges(reindexBatch)
}
return bound + resolved
}
// resolveRustTraitOverrides binds the unresolved EdgeOverrides the extractor
// emits for `impl Trait for Type` methods (target unresolved::<Trait>.<method>)
// to the trait declaration's method node. The trait may live in another file
// or crate, so the binding runs off the trait-method index rather than the
// caller's file. Returns the number of override edges bound.
func resolveRustTraitOverrides(g graph.Store, idx *rustScopeIndex) int {
var cands []*graph.Edge
fromIDs := make(map[string]struct{})
for e := range g.EdgesByKind(graph.EdgeOverrides) {
if e == nil || !graph.IsUnresolvedTarget(e.To) {
continue
}
if _, _, ok := parseRustOverrideTarget(e.To); !ok {
continue
}
cands = append(cands, e)
if e.From != "" {
fromIDs[e.From] = struct{}{}
}
}
if len(cands) == 0 {
return 0
}
fromList := make([]string, 0, len(fromIDs))
for id := range fromIDs {
fromList = append(fromList, id)
}
fromNodes := g.GetNodesByIDs(fromList)
bound := 0
var batch []graph.EdgeReindex
for _, e := range cands {
trait, method, _ := parseRustOverrideTarget(e.To)
from := fromNodes[e.From]
if from == nil || from.Language != "rust" {
continue
}
target := idx.uniqueTraitMethod(from.RepoPrefix, trait, method)
if target == "" || target == e.To {
continue
}
oldTo := e.To
e.To = target
e.Origin = graph.OriginASTResolved
e.Confidence = 1.0
e.ConfidenceLabel = graph.ConfidenceLabelFor(graph.EdgeOverrides, 1.0)
if e.Meta == nil {
e.Meta = map[string]any{}
}
e.Meta["rust_resolution"] = "trait_override"
batch = append(batch, graph.EdgeReindex{Edge: e, OldTo: oldTo})
bound++
}
if len(batch) > 0 {
g.ReindexEdges(batch)
}
return bound
}
// parseRustOverrideTarget splits an unresolved::<Trait>.<method> override
// target into its trait + method components.
func parseRustOverrideTarget(to string) (trait, method string, ok bool) {
name := graph.UnresolvedName(to)
i := strings.LastIndex(name, ".")
if i <= 0 || i >= len(name)-1 {
return "", "", false
}
return name[:i], name[i+1:], true
}
// rustScopeEdgeCandidate reports whether an unresolved call edge is one
// this pass can attempt: a path call (Meta["rust_path"] set), a self/Self
// selector call (Meta["rust_recv"] in {self, Self}), or a selector call on
// a typed receiver (Meta["receiver_type"] set) that the generic resolver
// left unresolved because it keys methods by their verbatim (generic)
// receiver. Every other selector call is left to the generic resolver.
func rustScopeEdgeCandidate(e *graph.Edge) bool {
if e.Meta == nil {
return false
}
if p, _ := e.Meta["rust_path"].(string); strings.Contains(p, "::") {
return true
}
if r, _ := e.Meta["rust_recv"].(string); r == "self" || r == "Self" {
return true
}
if rt, _ := e.Meta["receiver_type"].(string); rt != "" {
return true
}
if ex, _ := e.Meta["rust_recv_expr"].(string); strings.HasPrefix(ex, "self.") {
return true
}
return false
}
// rustScopeIndex holds the per-repo method/function lookup tables this
// pass binds against. lastOrigin / lastConfidence / lastReason carry the
// provenance of the most recent resolve() call so the edge-rewrite loop
// can stamp it without resolve() returning a struct.
type rustScopeIndex struct {
// methodsByOwner: (repo, ownerType) → method nodes of that type.
methodsByOwner map[rustOwnerKey][]*graph.Node
// freeFuncsByName: (repo, name) → free function nodes.
freeFuncsByName map[rustNameKey][]*graph.Node
// paramsByOwner: caller function/method ID → set of param names,
// for local-shadows-import precedence.
paramsByOwner map[string]map[string]struct{}
// fieldTypesByOwner: (repo, ownerType, fieldName) → declared field type
// (base name), for walking self.<field>.<field> receiver chains.
fieldTypesByOwner map[rustFieldKey]string
lastOrigin string
lastConfidence float64
lastReason string
}
type rustOwnerKey struct {
repo string
owner string
}
type rustNameKey struct {
repo string
name string
}
type rustFieldKey struct {
repo string
owner string
field string
}
// buildRustScopeIndex walks the graph once and indexes Rust method
// owners, free functions, and caller params. Returns nil when the graph
// has no Rust methods or functions (the pass is a no-op for non-Rust
// graphs).
func buildRustScopeIndex(g graph.Store) *rustScopeIndex {
idx := &rustScopeIndex{
methodsByOwner: map[rustOwnerKey][]*graph.Node{},
freeFuncsByName: map[rustNameKey][]*graph.Node{},
paramsByOwner: map[string]map[string]struct{}{},
fieldTypesByOwner: map[rustFieldKey]string{},
}
any := false
for n := range g.NodesByKind(graph.KindMethod) {
if n == nil || n.Language != "rust" {
continue
}
owner := nodeReceiverType(n)
if owner == "" {
continue
}
// Index under the verbatim owner AND a generics/lifetime-stripped
// base (Candidate<'a> -> Candidate) so a call qualifier or inferred
// receiver_type that names the base binds to a method whose impl type
// carries generic args. The module path is kept to avoid cross-module
// name collisions (io::Error stays io::Error).
for _, key := range rustOwnerLookupKeys(owner) {
k := rustOwnerKey{repo: n.RepoPrefix, owner: key}
idx.methodsByOwner[k] = append(idx.methodsByOwner[k], n)
}
any = true
}
for n := range g.NodesByKind(graph.KindFunction) {
if n == nil || n.Language != "rust" {
continue
}
idx.freeFuncsByName[rustNameKey{repo: n.RepoPrefix, name: n.Name}] = append(
idx.freeFuncsByName[rustNameKey{repo: n.RepoPrefix, name: n.Name}], n)
any = true
}
if !any {
return nil
}
// Params are read lazily-but-once: index every Rust param by its
// enclosing function/method ID for the shadow check.
for n := range g.NodesByKind(graph.KindParam) {
if n == nil || n.Language != "rust" {
continue
}
owner := enclosingFunctionForBinding(n.ID)
if owner == "" {
continue
}
set := idx.paramsByOwner[owner]
if set == nil {
set = map[string]struct{}{}
idx.paramsByOwner[owner] = set
}
set[n.Name] = struct{}{}
}
// Struct fields carry their declared type + owner in Meta, indexed by
// generics-stripped base names so a self.<field> chain can be walked.
for n := range g.NodesByKind(graph.KindField) {
if n == nil || n.Language != "rust" {
continue
}
owner, _ := n.Meta["receiver"].(string)
ft, _ := n.Meta["field_type"].(string)
if owner == "" || ft == "" {
continue
}
idx.fieldTypesByOwner[rustFieldKey{
repo: n.RepoPrefix,
owner: rustBaseTypeName(owner),
field: n.Name,
}] = rustBaseTypeName(ft)
}
return idx
}
// resolve returns the target node ID an unresolved Rust call edge should
// bind to, or "" when the call can't be resolved unambiguously. It also
// records the provenance (origin / confidence / reason) of a successful
// binding on the index for the caller to stamp.
func (idx *rustScopeIndex) resolve(e *graph.Edge, caller *graph.Node) string {
repo := caller.RepoPrefix
// Selector self/Self call: bind to a method of the caller's owner
// type. The caller is the enclosing impl method, so its receiver is
// the impl type.
if recv, _ := e.Meta["rust_recv"].(string); recv == "self" || recv == "Self" {
owner := nodeReceiverType(caller)
if owner == "" {
return ""
}
name := selectorCallName(e.To)
if name == "" {
return ""
}
if id := idx.uniqueMethod(repo, owner, name); id != "" {
idx.set(graph.OriginASTResolved, 0.92, "self_receiver")
return id
}
return ""
}
// Selector call on a self-rooted field-access receiver
// (`self.config.line_term.as_byte()`). Walk the field types from the
// enclosing impl type down the chain, then bind the method on the type
// the chain lands on.
if expr, _ := e.Meta["rust_recv_expr"].(string); strings.HasPrefix(expr, "self.") {
if name := selectorCallName(e.To); name != "" {
if t := idx.fieldWalk(repo, nodeReceiverType(caller), expr); t != "" {
if id := idx.uniqueMethod(repo, t, name); id != "" {
idx.set(graph.OriginASTResolved, 0.82, "field_receiver")
return id
}
}
}
}
// Selector call on a typed variable/param (`mat.buffer()` where
// `mat: &SinkMatch<'_>`). The generic resolver keys methods by their
// verbatim receiver ("SinkMatch<'b>"), so a generics-stripped inferred
// receiver_type ("SinkMatch") misses it. The scope index carries a
// base-name alias, so bind here when the type owns exactly one such
// method.
if rt, _ := e.Meta["receiver_type"].(string); rt != "" {
if name := selectorCallName(e.To); name != "" {
if id := idx.uniqueMethod(repo, rustBaseTypeName(rt), name); id != "" {
idx.set(graph.OriginASTResolved, 0.88, "receiver_type")
return id
}
}
}
path, _ := e.Meta["rust_path"].(string)
if !strings.Contains(path, "::") {
return ""
}
segments := strings.Split(path, "::")
last := segments[len(segments)-1]
if last == "" {
return ""
}
qualifier := segments[len(segments)-2]
switch {
case qualifier == "Self":
// Self::method() — same binding as the self selector case.
owner := nodeReceiverType(caller)
if owner == "" {
return ""
}
if id := idx.uniqueMethod(repo, owner, last); id != "" {
idx.set(graph.OriginASTResolved, 0.92, "self_path")
return id
}
return ""
case isRustTypeName(qualifier):
// Type::method() — bind to a method whose owner type is the
// qualifier. The receiver type is named explicitly in source, so
// this is structurally resolved within that type.
if id := idx.uniqueMethod(repo, qualifier, last); id != "" {
idx.set(graph.OriginASTResolved, 0.9, "impl_owner")
return id
}
// Ambiguous by type name alone — the same type name is defined in
// more than one crate/module (e.g. grep::regex::RegexMatcherBuilder
// and grep::pcre2::RegexMatcherBuilder). Disambiguate with the
// qualified path's crate/module segments: bind to the candidate
// whose file lives under a directory named by a path segment.
if id := idx.methodByPathSegments(repo, qualifier, last, segments); id != "" {
idx.set(graph.OriginASTResolved, 0.9, "impl_owner_path")
return id
}
// Still ambiguous, and the call named no disambiguating path segment
// (bare `RegexMatcherBuilder::new()`). Prefer the candidate defined in
// the caller's own crate: a same-crate associated-function call almost
// always means the same-crate type.
if id := idx.methodBySameCrate(repo, qualifier, last, caller.FilePath); id != "" {
idx.set(graph.OriginASTResolved, 0.85, "impl_owner_crate")
return id
}
return ""
default:
// Module path: crate::/super::/self::/<module>::func(). Gortex
// doesn't model the module tree, so bind the trailing segment to
// a free function in the same repo, preferring locality. Skipped
// when a same-named caller param shadows the import.
if idx.callerShadows(e.From, last) {
return ""
}
if id := idx.uniqueFreeFunc(repo, last, caller.FilePath); id != "" {
idx.set(graph.OriginASTInferred, 0.75, "module_path")
return id
}
return ""
}
}
func (idx *rustScopeIndex) set(origin string, conf float64, reason string) {
idx.lastOrigin = origin
idx.lastConfidence = conf
idx.lastReason = reason
}
// uniqueMethod returns the ID of the single method named `name` owned by
// (repo, owner), or "" when there is no match or the choice is
// ambiguous (more than one).
func (idx *rustScopeIndex) uniqueMethod(repo, owner, name string) string {
cands := idx.methodsByOwner[rustOwnerKey{repo: repo, owner: owner}]
var hit string
for _, m := range cands {
if m.Name != name {
continue
}
if hit != "" && hit != m.ID {
return "" // ambiguous
}
hit = m.ID
}
return hit
}
// methodByPathSegments disambiguates a Type::method call whose type name is
// defined in more than one crate/module by matching the qualified path's
// crate/module segments (grep::regex::Foo::bar -> a candidate whose file
// lives under a `regex` directory) against each candidate's file path.
// Returns the ID only when exactly one candidate matches a path segment.
func (idx *rustScopeIndex) methodByPathSegments(repo, owner, name string, segments []string) string {
cands := idx.methodsByOwner[rustOwnerKey{repo: repo, owner: owner}]
var hit string
for _, m := range cands {
if m.Name != name {
continue
}
for _, seg := range segments {
if seg == "" || seg == owner || seg == name {
continue
}
if strings.Contains(m.FilePath, "/"+seg+"/") {
if hit != "" && hit != m.ID {
return "" // more than one crate/module matched
}
hit = m.ID
break
}
}
}
return hit
}
// methodBySameCrate disambiguates a `Type::method` call that names no
// disambiguating path segment by preferring the candidate defined in the
// caller's own crate. Returns the ID only when exactly one candidate lives
// in that crate.
func (idx *rustScopeIndex) methodBySameCrate(repo, owner, name, callerFile string) string {
callerCrate := rustCrateOf(callerFile)
if callerCrate == "" {
return ""
}
cands := idx.methodsByOwner[rustOwnerKey{repo: repo, owner: owner}]
var hit string
for _, m := range cands {
if m.Name != name {
continue
}
if rustCrateOf(m.FilePath) != callerCrate {
continue
}
if hit != "" && hit != m.ID {
return "" // more than one candidate in the caller's crate
}
hit = m.ID
}
return hit
}
// fieldWalk resolves the type a self-rooted field-access receiver lands on.
// Given the enclosing impl type (`Searcher`) and a receiver expression
// (`self.config.line_term`), it walks each field via the field-type index —
// Searcher.config -> Config, Config.line_term -> LineTerminator — and
// returns the final type, or "" if any hop is unknown or ambiguous.
func (idx *rustScopeIndex) fieldWalk(repo, implType, expr string) string {
t := rustBaseTypeName(implType)
if t == "" {
return ""
}
fields := strings.Split(strings.TrimPrefix(expr, "self."), ".")
for _, f := range fields {
if f == "" {
return ""
}
next := idx.fieldTypesByOwner[rustFieldKey{repo: repo, owner: t, field: f}]
if next == "" {
return ""
}
t = next
}
return t
}
// rustCrateOf returns a stable identifier for the crate a Rust source file
// belongs to: the path up to (and excluding) the "/src/" segment that marks
// a cargo crate root (crates/regex/src/matcher.rs -> "crates/regex",
// myproj/src/lib.rs -> "myproj"). Files with no "/src/" segment — flat
// scripts, test-dir files, or synthetic fixtures — have no determinable
// crate and return "", so the same-crate tiebreaker stays conservative and
// never guesses across an unknown boundary.
func rustCrateOf(path string) string {
if i := strings.Index(path, "/src/"); i >= 0 {
return path[:i]
}
return ""
}
// uniqueTraitMethod returns the ID of the single trait-declaration method
// named `name` owned by trait `owner` in repo, or "" on no match or
// ambiguity. Only nodes marked Meta["trait_decl"]="true" qualify, so an
// inherent method on a same-named type is never mistaken for the trait's.
func (idx *rustScopeIndex) uniqueTraitMethod(repo, owner, name string) string {
cands := idx.methodsByOwner[rustOwnerKey{repo: repo, owner: owner}]
var hit string
for _, m := range cands {
if m.Name != name || m.Meta == nil {
continue
}
if td, _ := m.Meta["trait_decl"].(string); td != "true" {
continue
}
if hit != "" && hit != m.ID {
return ""
}
hit = m.ID
}
return hit
}
// uniqueFreeFunc returns the ID of a free function named `name` in repo,
// preferring a same-file candidate, then a same-directory candidate,
// then a unique candidate overall. Returns "" when nothing matches or
// the choice is ambiguous (more than one across different files with no
// locality tie-break).
func (idx *rustScopeIndex) uniqueFreeFunc(repo, name, callerFile string) string {
cands := idx.freeFuncsByName[rustNameKey{repo: repo, name: name}]
if len(cands) == 0 {
return ""
}
if len(cands) == 1 {
return cands[0].ID
}
callerDir := rustParentDir(callerFile)
var sameFile, sameDir []*graph.Node
for _, f := range cands {
if f.FilePath == callerFile {
sameFile = append(sameFile, f)
}
if rustParentDir(f.FilePath) == callerDir {
sameDir = append(sameDir, f)
}
}
if len(sameFile) == 1 {
return sameFile[0].ID
}
if len(sameFile) == 0 && len(sameDir) == 1 {
return sameDir[0].ID
}
return "" // ambiguous across files
}
// callerShadows reports whether the calling function/method declares a
// parameter named `name` — a local binding that shadows an import of
// the same identifier under Rust's name-resolution rules.
func (idx *rustScopeIndex) callerShadows(callerID, name string) bool {
set := idx.paramsByOwner[callerID]
if set == nil {
return false
}
_, ok := set[name]
return ok
}
// selectorCallName extracts the method name from a selector-call
// placeholder target of the form `unresolved::*.<name>` (or the
// per-repo `<repo>::unresolved::*.<name>` form).
func selectorCallName(to string) string {
name := graph.UnresolvedName(to)
if name == "" {
return ""
}
name = strings.TrimPrefix(name, "*.")
if i := strings.LastIndex(name, "."); i >= 0 {
name = name[i+1:]
}
return name
}
// isRustTypeName reports whether s looks like a Rust type path qualifier
// (UpperCamelCase) rather than a module/path keyword. Crate-relative
// keywords (crate/super/self) and lowercase module names are not types.
func isRustTypeName(s string) bool {
switch s {
case "", "crate", "super", "self", "Self":
return false
}
c := s[0]
return c >= 'A' && c <= 'Z'
}
// rustParentDir returns the slash-separated parent directory of a graph
// file path. Graph paths are slash-normalised, so a plain byte scan is
// correct on every OS.
func rustParentDir(path string) string {
if i := strings.LastIndexByte(path, '/'); i >= 0 {
return path[:i]
}
return ""
}
// rustOwnerLookupKeys returns the keys a method's verbatim owner type should
// be indexed under: the verbatim text plus a generics/lifetime/ref-stripped
// base (module path kept). "Candidate<'a>" -> ["Candidate<'a>", "Candidate"];
// "io::Error" -> ["io::Error"]; "Foo" -> ["Foo"].
func rustOwnerLookupKeys(owner string) []string {
keys := []string{owner}
if base := rustBaseTypeName(owner); base != "" && base != owner {
keys = append(keys, base)
}
return keys
}
// rustBaseTypeName strips references, a leading lifetime and generic args from
// a verbatim Rust type, keeping the module path: "&'a mut Candidate<'a>" ->
// "Candidate", "io::Error" -> "io::Error".
func rustBaseTypeName(s string) string {
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, "&mut ")
s = strings.TrimPrefix(s, "&")
s = strings.TrimSpace(s)
if strings.HasPrefix(s, "'") {
if i := strings.IndexByte(s, ' '); i >= 0 {
s = strings.TrimSpace(s[i+1:])
s = strings.TrimPrefix(s, "mut ")
s = strings.TrimSpace(s)
}
}
if i := strings.Index(s, "<"); i >= 0 {
s = strings.TrimSpace(s[:i])
}
return s
}