// equalObj reports how x and y differ. They are assumed to belong to // different universes so cannot be compared directly. func equalObj(x, y types.Object) error { if reflect.TypeOf(x) != reflect.TypeOf(y) { return fmt.Errorf("%T vs %T", x, y) } xt := x.Type() yt := y.Type() switch x.(type) { case *types.Var, *types.Func: // ok case *types.Const: xval := x.(*types.Const).Val() yval := y.(*types.Const).Val() // Use string comparison for floating-point values since rounding is permitted. if constant.Compare(xval, token.NEQ, yval) && !(xval.Kind() == constant.Float && xval.String() == yval.String()) { return fmt.Errorf("unequal constants %s vs %s", xval, yval) } case *types.TypeName: xt = xt.Underlying() yt = yt.Underlying() default: return fmt.Errorf("unexpected %T", x) } return equalType(xt, yt) }
func (constantFolderVisitor) VisitPost(expr Expr) (retExpr Expr) { defer func() { // go/constant operations can panic for a number of reasons (like division // by zero), but it's difficult to preemptively detect when they will. It's // safest to just recover here without folding the expression and let // normalization or evaluation deal with error handling. if r := recover(); r != nil { retExpr = expr } }() switch t := expr.(type) { case *ParenExpr: if cv, ok := t.Expr.(*NumVal); ok { return cv } case *UnaryExpr: if cv, ok := t.Expr.(*NumVal); ok { if token, ok := unaryOpToToken[t.Operator]; ok { return &NumVal{Value: constant.UnaryOp(token, cv.Value, 0)} } if token, ok := unaryOpToTokenIntOnly[t.Operator]; ok { if intVal, ok := cv.asConstantInt(); ok { return &NumVal{Value: constant.UnaryOp(token, intVal, 0)} } } } case *BinaryExpr: l, okL := t.Left.(*NumVal) r, okR := t.Right.(*NumVal) if okL && okR { if token, ok := binaryOpToToken[t.Operator]; ok { return &NumVal{Value: constant.BinaryOp(l.Value, token, r.Value)} } if token, ok := binaryOpToTokenIntOnly[t.Operator]; ok { if lInt, ok := l.asConstantInt(); ok { if rInt, ok := r.asConstantInt(); ok { return &NumVal{Value: constant.BinaryOp(lInt, token, rInt)} } } } if token, ok := binaryShiftOpToToken[t.Operator]; ok { if lInt, ok := l.asConstantInt(); ok { if rInt64, err := r.asInt64(); err == nil && rInt64 >= 0 { return &NumVal{Value: constant.Shift(lInt, token, uint(rInt64))} } } } } case *ComparisonExpr: l, okL := t.Left.(*NumVal) r, okR := t.Right.(*NumVal) if okL && okR { if token, ok := comparisonOpToToken[t.Operator]; ok { return MakeDBool(DBool(constant.Compare(l.Value, token, r.Value))) } } } return expr }
func constantCompare(op token.Token, x, y constant.Value) (r bool, err error) { defer func() { if ierr := recover(); ierr != nil { err = fmt.Errorf("%v", ierr) } }() r = constant.Compare(x, op, y) return }
func (check *Checker) comparison(x, y *operand, op token.Token) { // spec: "In any comparison, the first operand must be assignable // to the type of the second operand, or vice versa." err := "" if x.assignableTo(check.conf, y.typ, nil) || y.assignableTo(check.conf, x.typ, nil) { defined := false switch op { case token.EQL, token.NEQ: // spec: "The equality operators == and != apply to operands that are comparable." defined = Comparable(x.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) case token.LSS, token.LEQ, token.GTR, token.GEQ: // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." defined = isOrdered(x.typ) default: unreachable() } if !defined { typ := x.typ if x.isNil() { typ = y.typ } err = check.sprintf("operator %s not defined for %s", op, typ) } } else { err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) } if err != "" { check.errorf(x.pos(), "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) x.mode = invalid return } if x.mode == constant_ && y.mode == constant_ { x.val = constant.MakeBool(constant.Compare(x.val, op, y.val)) // The operands are never materialized; no need to update // their types. } else { x.mode = value // The operands have now their final types, which at run- // time will be materialized. Update the expression trees. // If the current types are untyped, the materialized type // is the respective default type. check.updateExprType(x.expr, defaultType(x.typ), true) check.updateExprType(y.expr, defaultType(y.typ), true) } // spec: "Comparison operators compare two operands and yield // an untyped boolean value." x.typ = Typ[UntypedBool] }
func checkConstValue(t *testing.T, prog *ssa.Program, obj *types.Const) { c := prog.ConstValue(obj) // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging if c == nil { t.Errorf("ConstValue(%s) == nil", obj) return } if !types.Identical(c.Type(), obj.Type()) { t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type()) return } if obj.Name() != "nil" { if !exact.Compare(c.Value, token.EQL, obj.Val()) { t.Errorf("ConstValue(%s).Value (%s) != %s", obj, c.Value, obj.Val()) return } } }
func compare(a, b interface{}, tok token.Token) bool { vala := reflect.ValueOf(a) valb := reflect.ValueOf(b) ak := vala.Kind() bk := valb.Kind() switch { case ak >= reflect.Int && ak <= reflect.Int64: if bk >= reflect.Int && bk <= reflect.Int64 { return constant.Compare(constant.MakeInt64(vala.Int()), tok, constant.MakeInt64(valb.Int())) } if bk == reflect.Float32 || bk == reflect.Float64 { return constant.Compare(constant.MakeFloat64(float64(vala.Int())), tok, constant.MakeFloat64(valb.Float())) } if bk == reflect.String { bla, err := strconv.ParseFloat(valb.String(), 64) if err != nil { return false } return constant.Compare(constant.MakeFloat64(float64(vala.Int())), tok, constant.MakeFloat64(bla)) } case ak == reflect.Float32 || ak == reflect.Float64: if bk == reflect.Float32 || bk == reflect.Float64 { return constant.Compare(constant.MakeFloat64(vala.Float()), tok, constant.MakeFloat64(valb.Float())) } if bk >= reflect.Int && bk <= reflect.Int64 { return constant.Compare(constant.MakeFloat64(vala.Float()), tok, constant.MakeFloat64(float64(valb.Int()))) } if bk == reflect.String { bla, err := strconv.ParseFloat(valb.String(), 64) if err != nil { return false } return constant.Compare(constant.MakeFloat64(vala.Float()), tok, constant.MakeFloat64(bla)) } case ak == reflect.String: if bk == reflect.String { return constant.Compare(constant.MakeString(vala.String()), tok, constant.MakeString(valb.String())) } } if reflect.TypeOf(a).String() == "time.Time" && reflect.TypeOf(b).String() == "time.Time" { var x, y int64 x = 1 if vala.MethodByName("Equal").Call([]reflect.Value{valb})[0].Bool() { y = 1 } else if vala.MethodByName("Before").Call([]reflect.Value{valb})[0].Bool() { y = 2 } return constant.Compare(constant.MakeInt64(x), tok, constant.MakeInt64(y)) } if tok == token.EQL { return reflect.DeepEqual(a, b) } return false }
// matchExpr reports whether pattern x matches y. // // If tr.allowWildcards, Idents in x that refer to parameters are // treated as wildcards, and match any y that is assignable to the // parameter type; matchExpr records this correspondence in tr.env. // Otherwise, matchExpr simply reports whether the two trees are // equivalent. // // A wildcard appearing more than once in the pattern must // consistently match the same tree. // func (tr *Transformer) matchExpr(x, y ast.Expr) bool { if x == nil && y == nil { return true } if x == nil || y == nil { return false } x = unparen(x) y = unparen(y) // Is x a wildcard? (a reference to a 'before' parameter) if xobj, ok := tr.wildcardObj(x); ok { return tr.matchWildcard(xobj, y) } // Object identifiers (including pkg-qualified ones) // are handled semantically, not syntactically. xobj := isRef(x, tr.info) yobj := isRef(y, tr.info) if xobj != nil { return xobj == yobj } if yobj != nil { return false } // TODO(adonovan): audit: we cannot assume these ast.Exprs // contain non-nil pointers. e.g. ImportSpec.Name may be a // nil *ast.Ident. if reflect.TypeOf(x) != reflect.TypeOf(y) { return false } switch x := x.(type) { case *ast.Ident: log.Fatalf("unexpected Ident: %s", astString(tr.fset, x)) case *ast.BasicLit: y := y.(*ast.BasicLit) xval := exact.MakeFromLiteral(x.Value, x.Kind, 0) yval := exact.MakeFromLiteral(y.Value, y.Kind, 0) return exact.Compare(xval, token.EQL, yval) case *ast.FuncLit: // func literals (and thus statement syntax) never match. return false case *ast.CompositeLit: y := y.(*ast.CompositeLit) return (x.Type == nil) == (y.Type == nil) && (x.Type == nil || tr.matchType(x.Type, y.Type)) && tr.matchExprs(x.Elts, y.Elts) case *ast.SelectorExpr: y := y.(*ast.SelectorExpr) return tr.matchSelectorExpr(x, y) && tr.info.Selections[x].Obj() == tr.info.Selections[y].Obj() case *ast.IndexExpr: y := y.(*ast.IndexExpr) return tr.matchExpr(x.X, y.X) && tr.matchExpr(x.Index, y.Index) case *ast.SliceExpr: y := y.(*ast.SliceExpr) return tr.matchExpr(x.X, y.X) && tr.matchExpr(x.Low, y.Low) && tr.matchExpr(x.High, y.High) && tr.matchExpr(x.Max, y.Max) && x.Slice3 == y.Slice3 case *ast.TypeAssertExpr: y := y.(*ast.TypeAssertExpr) return tr.matchExpr(x.X, y.X) && tr.matchType(x.Type, y.Type) case *ast.CallExpr: y := y.(*ast.CallExpr) match := tr.matchExpr // function call if tr.info.Types[x.Fun].IsType() { match = tr.matchType // type conversion } return x.Ellipsis.IsValid() == y.Ellipsis.IsValid() && match(x.Fun, y.Fun) && tr.matchExprs(x.Args, y.Args) case *ast.StarExpr: y := y.(*ast.StarExpr) return tr.matchExpr(x.X, y.X) case *ast.UnaryExpr: y := y.(*ast.UnaryExpr) return x.Op == y.Op && tr.matchExpr(x.X, y.X) case *ast.BinaryExpr: y := y.(*ast.BinaryExpr) return x.Op == y.Op && tr.matchExpr(x.X, y.X) && tr.matchExpr(x.Y, y.Y) case *ast.KeyValueExpr: y := y.(*ast.KeyValueExpr) return tr.matchExpr(x.Key, y.Key) && tr.matchExpr(x.Value, y.Value) } panic(fmt.Sprintf("unhandled AST node type: %T", x)) }
func (constantFolderVisitor) VisitPost(expr Expr) (retExpr Expr) { defer func() { // go/constant operations can panic for a number of reasons (like division // by zero), but it's difficult to preemptively detect when they will. It's // safest to just recover here without folding the expression and let // normalization or evaluation deal with error handling. if r := recover(); r != nil { retExpr = expr } }() switch t := expr.(type) { case *ParenExpr: switch cv := t.Expr.(type) { case *NumVal, *StrVal: return cv } case *UnaryExpr: switch cv := t.Expr.(type) { case *NumVal: if token, ok := unaryOpToToken[t.Operator]; ok { return &NumVal{Value: constant.UnaryOp(token, cv.Value, 0)} } if token, ok := unaryOpToTokenIntOnly[t.Operator]; ok { if intVal, ok := cv.asConstantInt(); ok { return &NumVal{Value: constant.UnaryOp(token, intVal, 0)} } } } case *BinaryExpr: switch l := t.Left.(type) { case *NumVal: if r, ok := t.Right.(*NumVal); ok { if token, ok := binaryOpToToken[t.Operator]; ok { return &NumVal{Value: constant.BinaryOp(l.Value, token, r.Value)} } if token, ok := binaryOpToTokenIntOnly[t.Operator]; ok { if lInt, ok := l.asConstantInt(); ok { if rInt, ok := r.asConstantInt(); ok { return &NumVal{Value: constant.BinaryOp(lInt, token, rInt)} } } } if token, ok := binaryShiftOpToToken[t.Operator]; ok { if lInt, ok := l.asConstantInt(); ok { if rInt64, err := r.asInt64(); err == nil && rInt64 >= 0 { return &NumVal{Value: constant.Shift(lInt, token, uint(rInt64))} } } } } case *StrVal: if r, ok := t.Right.(*StrVal); ok { switch t.Operator { case Concat: // When folding string-like constants, if either was byte-escaped, // the result is also considered byte escaped. return &StrVal{s: l.s + r.s, bytesEsc: l.bytesEsc || r.bytesEsc} } } } case *ComparisonExpr: switch l := t.Left.(type) { case *NumVal: if r, ok := t.Right.(*NumVal); ok { if token, ok := comparisonOpToToken[t.Operator]; ok { return MakeDBool(DBool(constant.Compare(l.Value, token, r.Value))) } } case *StrVal: // ComparisonExpr folding for String-like constants is not significantly different // from constant evalutation during normalization (because both should be exact, // unlike numeric comparisons). Still, folding these comparisons when possible here // can reduce the amount of work performed during type checking, can reduce necessary // allocations, and maintains symmetry with numeric constants. if r, ok := t.Right.(*StrVal); ok { switch t.Operator { case EQ: return MakeDBool(DBool(l.s == r.s)) case NE: return MakeDBool(DBool(l.s != r.s)) case LT: return MakeDBool(DBool(l.s < r.s)) case LE: return MakeDBool(DBool(l.s <= r.s)) case GT: return MakeDBool(DBool(l.s > r.s)) case GE: return MakeDBool(DBool(l.s >= r.s)) } } } } return expr }