golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/apidiff/messageset.go (about)

     1  // TODO: show that two-non-empty dotjoin can happen, by using an anon struct as a field type
     2  // TODO: don't report removed/changed methods for both value and pointer method sets?
     3  
     4  package apidiff
     5  
     6  import (
     7  	"fmt"
     8  	"go/types"
     9  	"sort"
    10  	"strings"
    11  )
    12  
    13  // objectWithSide contains an object, and information on which side (old or new)
    14  // of the comparison it relates to. This matters when need to express the object's
    15  // package path, relative to the root path of the comparison, as the old and new
    16  // sides can have different roots (e.g. comparing somepackage/v2 vs. somepackage/v3).
    17  type objectWithSide struct {
    18  	object types.Object
    19  	isNew  bool
    20  }
    21  
    22  // There can be at most one message for each object or part thereof.
    23  // Parts include interface methods and struct fields.
    24  //
    25  // The part thing is necessary. Method (Func) objects have sufficient info, but field
    26  // Vars do not: they just have a field name and a type, without the enclosing struct.
    27  type messageSet map[objectWithSide]map[string]string
    28  
    29  // Add a message for obj and part, overwriting a previous message
    30  // (shouldn't happen).
    31  // obj is required but part can be empty.
    32  func (m messageSet) add(obj objectWithSide, part, msg string) {
    33  	s := m[obj]
    34  	if s == nil {
    35  		s = map[string]string{}
    36  		m[obj] = s
    37  	}
    38  	if f, ok := s[part]; ok && f != msg {
    39  		fmt.Printf("! second, different message for obj %s, isNew %v, part %q\n", obj.object, obj.isNew, part)
    40  		fmt.Printf("  first:  %s\n", f)
    41  		fmt.Printf("  second: %s\n", msg)
    42  	}
    43  	s[part] = msg
    44  }
    45  
    46  func (m messageSet) collect(oldRootPackagePath, newRootPackagePath string) []string {
    47  	var s []string
    48  	for obj, parts := range m {
    49  		rootPackagePath := oldRootPackagePath
    50  		if obj.isNew {
    51  			rootPackagePath = newRootPackagePath
    52  		}
    53  
    54  		// Format each object name relative to its own package.
    55  		objstring := objectString(obj.object, rootPackagePath)
    56  		for part, msg := range parts {
    57  			var p string
    58  
    59  			if strings.HasPrefix(part, ",") {
    60  				p = objstring + part
    61  			} else {
    62  				p = dotjoin(objstring, part)
    63  			}
    64  			s = append(s, p+": "+msg)
    65  		}
    66  	}
    67  	sort.Strings(s)
    68  	return s
    69  }
    70  
    71  func objectString(obj types.Object, rootPackagePath string) string {
    72  	thisPackagePath := obj.Pkg().Path()
    73  
    74  	var packagePrefix string
    75  	if thisPackagePath == rootPackagePath {
    76  		// obj is in same package as the diff operation root - no prefix
    77  		packagePrefix = ""
    78  	} else if strings.HasPrefix(thisPackagePath, rootPackagePath+"/") {
    79  		// obj is in a child package compared to the diff operation root - use a
    80  		// prefix starting with "./" to emphasise the relative nature
    81  		packagePrefix = "./" + thisPackagePath[len(rootPackagePath)+1:] + "."
    82  	} else {
    83  		// obj is outside the diff operation root - display full path. This can
    84  		// happen if there is a need to report a change in a type in an unrelated
    85  		// package, because it has been used as the underlying type in a type
    86  		// definition in the package being processed, for example.
    87  		packagePrefix = thisPackagePath + "."
    88  	}
    89  
    90  	if f, ok := obj.(*types.Func); ok {
    91  		sig := f.Type().(*types.Signature)
    92  		if recv := sig.Recv(); recv != nil {
    93  			tn := types.TypeString(recv.Type(), types.RelativeTo(obj.Pkg()))
    94  			if tn[0] == '*' {
    95  				tn = "(" + tn + ")"
    96  			}
    97  			return fmt.Sprintf("%s%s.%s", packagePrefix, tn, obj.Name())
    98  		}
    99  	}
   100  	return fmt.Sprintf("%s%s", packagePrefix, obj.Name())
   101  }
   102  
   103  func dotjoin(s1, s2 string) string {
   104  	if s1 == "" {
   105  		return s2
   106  	}
   107  	if s2 == "" {
   108  		return s1
   109  	}
   110  	return s1 + "." + s2
   111  }