interp: apply integer division when appropriate
When working with an untyped const expression involving a division, if
the default type of the result should be an int (for example because the
default types of all the operands are ints as well), then we should make
sure that the operation that is applied is indeed an integer division,
and that the type of the result is not a float.
This is achieved by using the QUO_ASSIGN operator, instead of the QUO
operator.
This should fix several problems lurking around, and it notably fixes
one of the visible consequences, which is a systematic panic when using
the REPL as a "calculator".
This incidentally also allows us to revert what was done in
5dfc3b86dc since it now turns out it was
just a hack to fix one of the symptoms.
Fixes #864
This commit is contained in:
14
_test/const17.go
Normal file
14
_test/const17.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
var t [7/3]int
|
||||
|
||||
func main() {
|
||||
t[0] = 3/2
|
||||
t[1] = 5/2
|
||||
fmt.Println(t)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// [1 2]
|
||||
@@ -72,6 +72,9 @@ Options:
|
||||
include unsafe symbols.
|
||||
|
||||
Debugging support (may be removed at any time):
|
||||
YAEGI_PROMPT=1
|
||||
Force enable the printing of the REPL prompt and the result of last instruction,
|
||||
even if stdin is not a terminal.
|
||||
YAEGI_AST_DOT=1
|
||||
Generate and display graphviz dot of AST with dotty(1)
|
||||
YAEGI_CFG_DOT=1
|
||||
|
||||
@@ -196,6 +196,18 @@ func {{$name}}Const(n *node) {
|
||||
{{- if $op.Shift}}
|
||||
v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(vUint(v1)))
|
||||
n.rval.Set(reflect.ValueOf(v))
|
||||
{{- else if (eq $op.Name "/")}}
|
||||
var operator token.Token
|
||||
// When the result of the operation is expected to be an int (because both
|
||||
// operands are ints), we want to force the type of the whole expression to be an
|
||||
// int (and not a float), which is achieved by using the QUO_ASSIGN operator.
|
||||
if n.typ.untyped && isInt(n.typ.rtype) {
|
||||
operator = token.QUO_ASSIGN
|
||||
} else {
|
||||
operator = token.QUO
|
||||
}
|
||||
v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1))
|
||||
n.rval.Set(reflect.ValueOf(v))
|
||||
{{- else}}
|
||||
v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1))
|
||||
n.rval.Set(reflect.ValueOf(v))
|
||||
|
||||
@@ -785,14 +785,7 @@ func (interp *Interpreter) REPL() (reflect.Value, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getPrompt returns a function which prints a prompt only if input is a terminal.
|
||||
func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) {
|
||||
s, ok := in.(interface{ Stat() (os.FileInfo, error) })
|
||||
if !ok {
|
||||
return func(reflect.Value) {}
|
||||
}
|
||||
stat, err := s.Stat()
|
||||
if err == nil && stat.Mode()&os.ModeCharDevice != 0 {
|
||||
func doPrompt(out io.Writer) func(v reflect.Value) {
|
||||
return func(v reflect.Value) {
|
||||
if v.IsValid() {
|
||||
fmt.Fprintln(out, ":", v)
|
||||
@@ -800,5 +793,20 @@ func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) {
|
||||
fmt.Fprint(out, "> ")
|
||||
}
|
||||
}
|
||||
|
||||
// getPrompt returns a function which prints a prompt only if input is a terminal.
|
||||
func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) {
|
||||
forcePrompt, _ := strconv.ParseBool(os.Getenv("YAEGI_PROMPT"))
|
||||
if forcePrompt {
|
||||
return doPrompt(out)
|
||||
}
|
||||
s, ok := in.(interface{ Stat() (os.FileInfo, error) })
|
||||
if !ok {
|
||||
return func(reflect.Value) {}
|
||||
}
|
||||
stat, err := s.Stat()
|
||||
if err == nil && stat.Mode()&os.ModeCharDevice != 0 {
|
||||
return doPrompt(out)
|
||||
}
|
||||
return func(reflect.Value) {}
|
||||
}
|
||||
|
||||
@@ -1331,3 +1331,91 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
|
||||
}
|
||||
return time.Duration(float64(timeout) * CITimeoutMultiplier)
|
||||
}
|
||||
|
||||
func TestREPLDivision(t *testing.T) {
|
||||
_ = os.Setenv("YAEGI_PROMPT", "1")
|
||||
defer func() {
|
||||
_ = os.Setenv("YAEGI_PROMPT", "0")
|
||||
}()
|
||||
allDone := make(chan bool)
|
||||
runREPL := func() {
|
||||
done := make(chan error)
|
||||
pinin, poutin := io.Pipe()
|
||||
pinout, poutout := io.Pipe()
|
||||
i := interp.New(interp.Options{Stdin: pinin, Stdout: poutout})
|
||||
i.Use(stdlib.Symbols)
|
||||
|
||||
go func() {
|
||||
_, _ = i.REPL()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
_ = pinin.Close()
|
||||
_ = poutin.Close()
|
||||
_ = pinout.Close()
|
||||
_ = poutout.Close()
|
||||
allDone <- true
|
||||
}()
|
||||
|
||||
input := []string{
|
||||
`1/1`,
|
||||
`7/3`,
|
||||
`16/5`,
|
||||
`3./2`, // float
|
||||
}
|
||||
output := []string{
|
||||
`1`,
|
||||
`2`,
|
||||
`3`,
|
||||
`1.5`,
|
||||
}
|
||||
|
||||
go func() {
|
||||
sc := bufio.NewScanner(pinout)
|
||||
k := 0
|
||||
for sc.Scan() {
|
||||
l := sc.Text()
|
||||
if l != "> : "+output[k] {
|
||||
done <- fmt.Errorf("unexpected output, want %q, got %q", output[k], l)
|
||||
return
|
||||
}
|
||||
k++
|
||||
if k > 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
for _, v := range input {
|
||||
in := strings.NewReader(v + "\n")
|
||||
if _, err := io.Copy(poutin, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
default:
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
runREPL()
|
||||
}()
|
||||
|
||||
timeout := time.NewTimer(10 * time.Second)
|
||||
select {
|
||||
case <-allDone:
|
||||
case <-timeout.C:
|
||||
t.Fatal("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
11
interp/op.go
11
interp/op.go
@@ -701,7 +701,16 @@ func quoConst(n *node) {
|
||||
n.rval = reflect.New(t).Elem()
|
||||
switch {
|
||||
case isConst:
|
||||
v := constant.BinaryOp(vConstantValue(v0), token.QUO, vConstantValue(v1))
|
||||
var operator token.Token
|
||||
// When the result of the operation is expected to be an int (because both
|
||||
// operands are ints), we want to force the type of the whole expression to be an
|
||||
// int (and not a float), which is achieved by using the QUO_ASSIGN operator.
|
||||
if n.typ.untyped && isInt(n.typ.rtype) {
|
||||
operator = token.QUO_ASSIGN
|
||||
} else {
|
||||
operator = token.QUO
|
||||
}
|
||||
v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1))
|
||||
n.rval.Set(reflect.ValueOf(v))
|
||||
case isComplex(t):
|
||||
n.rval.SetComplex(vComplex(v0) / vComplex(v1))
|
||||
|
||||
@@ -1040,24 +1040,10 @@ func (check typecheck) convertUntyped(n *node, typ *itype) error {
|
||||
return convErr
|
||||
}
|
||||
|
||||
isFloatToIntDivision := false
|
||||
if err := check.representable(n, rtyp); err != nil {
|
||||
if !isInt(rtyp) || n.action != aQuo {
|
||||
return err
|
||||
}
|
||||
// retry in the case of a division, and pretend we want a float. Because if we
|
||||
// can represent a float, then it follows that we can represent the integer
|
||||
// part of that float as an int.
|
||||
if err := check.representable(n, reflect.TypeOf(1.0)); err != nil {
|
||||
return err
|
||||
}
|
||||
isFloatToIntDivision = true
|
||||
}
|
||||
if isFloatToIntDivision {
|
||||
n.rval, err = check.convertConstFloatToInt(n.rval)
|
||||
} else {
|
||||
n.rval, err = check.convertConst(n.rval, rtyp)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, errCantConvert) {
|
||||
return convErr
|
||||
@@ -1099,22 +1085,6 @@ func (check typecheck) representable(n *node, t reflect.Type) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (check typecheck) convertConstFloatToInt(v reflect.Value) (reflect.Value, error) {
|
||||
if !v.IsValid() {
|
||||
return v, errors.New("invalid float reflect value")
|
||||
}
|
||||
c, ok := v.Interface().(constant.Value)
|
||||
if !ok {
|
||||
return v, errors.New("unexpected non-constant value")
|
||||
}
|
||||
|
||||
if constant.ToFloat(c).Kind() != constant.Float {
|
||||
return v, errors.New("const value cannot be converted to float")
|
||||
}
|
||||
fl, _ := constant.Float64Val(c)
|
||||
return reflect.ValueOf(int(fl)).Convert(reflect.TypeOf(1.0)), nil
|
||||
}
|
||||
|
||||
func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) {
|
||||
if !v.IsValid() {
|
||||
// TODO(nick): This should be an error as the const is in the frame which is undesirable.
|
||||
|
||||
Reference in New Issue
Block a user