Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Groovy constants can be incremented?

In Groovy Console , ++(++(++(++1++)++)++)++ evaluates to 5 !
Why ?
I would expect an error that literal constants can not be incremented !

Pre-Increment gives the next Integer, while Post-Increment gives the same Integer, but I would expect both to throw errors when used with literal constants.

[[ I am running this on Windows 10 with Groovy 2.4.10 with Java 1.8.0_101 ]]

like image 673
Prem Avatar asked May 18 '17 15:05

Prem


People also ask

How do you increment a variable by 1?

The increment operator changes a variable by the value of one. Instead of writing varOne = varOne + 1; you can write varOne++; and it will do the same thing.

What does [:] mean in Groovy?

[:] creates an empty Map. The colon is there to distinguish it from [] , which creates an empty List. This groovy code: def foo = [:]

What does =~ do in Groovy?

In groovy, the ==~ operator (aka the "match" operator) is used for regular expression matching. !=

What is ${} in Groovy?

String interpolation. Any Groovy expression can be interpolated in all string literals, apart from single and triple-single-quoted strings. Interpolation is the act of replacing a placeholder in the string with its value upon evaluation of the string. The placeholder expressions are surrounded by ${} .


1 Answers

Nice question, I'd love to see an authoritative answer.

I kept a tab open on this and still see nothing, so I investigated myself. If you have the patience to read that, below is the perspective of a simple Groovy user (yours truly), looking at this with the tools I have and incomplete knowledge (hopefully not too mistaken).

In short: the language design & compiler implementation care more about the power & flexibility offered by transformations, and this apparently comes at the cost of relaxing the strictness.

Groovy is very permissive by design, and sometimes that might be a bit too much.

  • if (condition) int foo=1 is rejected by javac with "Declaration is not allowed here", because it makes no sense to declare a variable there, there must be a mistake, better check the code, right? Naaah it's OK for Groovy.

  • private fields and methods are not private.

  • final is applied inconsistently. You can increment a final int. That will be fixed in 2.5 though, see below.

Difference treatment of final between Groovy 2.4 & 2.5:

$ ./groovy-2.4.11/bin/groovy -e "final j=0; println(++j)"
1
$ ./groovy-2.5.0-alpha-1/bin/groovy -e "final j=0; println(++j)"
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
script_from_command_line: 1: The variable [j] is declared final but is reassigned
. At [1:20]  @ line 1, column 20.
   final j=0; println(++j)
                      ^
1 error

But no, Groovy 2.5 still does not prevent from incrementing a constant:

$ ./groovy-2.4.11/bin/groovy -e "println(++1)"
2
$ ./groovy-2.5.0-alpha-1/bin/groovy -e "println(++1)"
2

So what's happening there with ++1?

I think the first important thing is Groovy is not Java, and it does not transform to Java language. It compiles to bytecode. And here ++ is vastly different in Groovy & Java.

Groovy has 9 compilation phases (see org.codehaus.groovy.control.CompilePhase): initialization, parsing, conversion, semantic analysis, canonicalization, instruction selection, class generation, output, finalization.

We can explore them with GroovyConsole. Let's start it on this simple snippet:

def foo() {
    ++1
}

I see nothing in Compilation and Parsing phases, but at Conversion phase, we get something interesting:

public java.lang.Object foo() {
    ++(1)
}

The constant is surrounded by parentheses. Hmm. Interesting, notice we can do ++(2+2) and get 5. There is no variable to increment here... It seems ++ is just a shortcut for a method like incrementThis(thing), which either increments the thing and returns it, or if the thing is a constant, it adds 1 to the thing and returns the result.

In my IDE if I ctrl-click on ++ it gets me to org.codehaus.groovy.runtime.DefaultGroovyMethods.next(Number self):

/**
 * Increment a Number by one.
 *
 * @param self a Number
 * @return an incremented Number
 * @since 1.0
 */
public static Number next(Number self) {
    return NumberNumberPlus.plus(self, ONE);
}

If I track that down, it only ends up doing self + ONE, so I don't know where the alteration of self happens, and I think it's not so simple. I mean ++(x) is not just a shortcut to DefaultGroovyMethods.next(x), there is more to it.

So let's see what happens if I compile the following with javac and groovyc respectively:

//Wat.java:
public class Wat {
    public int foo() {
        int i=1;
        return ++i;
    }
}

//wat.groovy:
def foo() {
  ++1
}

Here is what I get (Java 1.8, Groovy 2.4, focusing on the relevant part):

// Java:
0: iconst_1                // load int value 1 onto stack
1: istore_1                // store int value into variable #1
2: iinc          1, 1      // increment local variable #1 by 1
5: iload_1                 // load int value from local variable #1
6: ireturn                 // return that integer value

// Groovy:
43: iconst_1               // load int value 1 onto stack
44: iconst_1               // load another int value 1 onto stack
45: iadd                   // add those 2 ints (result stored on stack in place of first int, stack is popped by 1)
46: invokestatic  #58      // Invoke method java/lang/Integer.valueOf(previous result)
49: areturn                // Return that new Integer result

So for Groovy ++1 is equivalent to Integer.valueOf(1+1) (and strictness simply takes a backseat).

But what about def foo(i) { ++i }? I compiled that, i is obviously an Object, but I don't understand the resulting bytecode, I still see no reference to DefaultGroovyMethods.next or NumberNumberPlus.plus. That's where I think this analysis falls short, and would love to see an authoritative answer.

like image 61
Hugues M. Avatar answered Nov 15 '22 07:11

Hugues M.