/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.codehaus.groovy.classgen.asm.sc

import groovy.transform.stc.BugsSTCTest

/**
 * Unit tests for static type checking : bugs.
 */
class BugsStaticCompileTest extends BugsSTCTest implements StaticCompilationTestSupport {

    void testGroovy5498PropertyAccess() {
        assertScript '''
            class Test {

              List getListVar() {
                new ArrayList()
              }

              void someMethod() {
                 def t = new Object()
                 t = this

                 t.getListVar()     //No error here
                 t.listVar          //error is being reported here
                 assert t.listVar == t.getListVar()
              }
            }
            new Test().someMethod()
        '''
    }

    // GROOVY-5512
    void testCreateRangeInInnerClass() {
        def shell = new GroovyShell()
        shell.evaluate '''
            class Outer {
                static class Inner {
                    @groovy.transform.CompileStatic
                    int m() {
                        int x = 0
                        for (int i in 1..10) {x++}
                        x
                    }
                }
            }
            assert new Outer.Inner().m() == 10
        '''
    }

    // GROOVY-5526
    void testAssertEqualsShouldNotThrowVerifyError() {
        assertScript '''
            import static org.junit.Assert.*;
            import groovy.transform.CompileStatic;

            class CompilerBugs {

              public static void main(String[] args) {
                int expected = 0
                assertEquals(expected, args.length)
              }

            }
        '''
    }

    // GROOVY-5529
    void testStaticCompilationOfClosureWhenSingleMethodAnnotated() {
        new GroovyShell().evaluate '''import groovy.transform.ASTTest
        import static org.codehaus.groovy.control.CompilePhase.*

        interface Row {
            int getKey()
        }

        class RowImpl implements Row {
            int getKey() { 1 }
        }

        @groovy.transform.CompileStatic
        def test() {
            def rows = [new RowImpl(), new RowImpl(), new RowImpl()]

            rows.each { Row row ->
                println row.key
            }
        }

        test()
        '''
    }

    // GROOVY-5536
    void testShouldNotThrowVerifyErrorWithNullDereferenceInIf() {
        assertScript '''
                boolean getDescriptorForPlugin(File pluginDir) {
                    if (pluginDir?.exists()) { true } else { false }
                }
                assert getDescriptorForPlugin(null) == false
        '''
    }

    // GROOVY-
    void testPowerShouldNotThrowVerifyError() {
        assertScript '''int squarePlusOne(int num) {
                num ** num + 1
            }
            assert squarePlusOne(2) == 5
        '''
    }

    // GROOVY-5570
    void testShouldNotThrowVerifyErrorRegisterContainsWrongType() {
        assertScript '''
                void foo() {
                boolean idx = false
                def cl = { idx }
                }
            '''
        assertScript '''
                void foo() {
                int idx = 0
                def cl = { idx }
                }
            '''
    }

    // GROOVY-5572
    void testTernaryOperatorWithNull() {
        assertScript '''
            assert (true ? null : true) == null
        '''
    }

    // GROOVY-5564
    void testSkipStaticCompile() {
        new GroovyShell().evaluate '''import groovy.transform.CompileStatic
            import static groovy.transform.TypeCheckingMode.SKIP

            @CompileStatic
            class A {
                @CompileStatic(SKIP)
                String toString(Object o) { o }
            }

            def a = new A()
            assert a.toString('foo')=='foo'
            assert a.toString(1) == '1'
            '''
    }

    // GROOVY-5586
    void testCanonicalInInnerClass() {
        new GroovyShell().evaluate '''import groovy.transform.*
            @CompileStatic
            class CanonicalStaticTest extends GroovyTestCase {
              @Canonical class Thing {
                String stuff
              }

              Thing testCanonical() {
                new Thing()
              }
            }
            assert new CanonicalStaticTest().testCanonical().toString() == 'CanonicalStaticTest$Thing(null)'
        '''
    }

    // GROOVY-5607
    // duplicate of GROOVY-5573, see ArraysAndCollectionsSTCTest#testArrayNewInstance()
    void testStaticNewInstanceMethodClash() {
        assertScript '''
            class Sql {
                static Sql newInstance(String s1, String s2, String s3, String s4) {
                    new Sql()
                }
            }

            @groovy.transform.CompileStatic
            class Main {
                void test() {
                    @ASTTest(phase=INSTRUCTION_SELECTION, value= {
                        assert node.rightExpression.getNodeMetaData(INFERRED_TYPE).nameWithoutPackage == 'Sql'
                    })
                    def sql = Sql.newInstance("a", "b", "c", "d")
                }
            }

            new Main().test()
        '''
    }

    void testCompileStaticTwiceShouldNotBeAProblem() {
        new GroovyShell().evaluate '''import groovy.transform.CompileStatic
        @CompileStatic
        class Tool {
            @CompileStatic // annotated too, even if class is already annotated
            String relativePath(File relbase, File file) {
                def pathParts = []
                def currentFile = file
                while (currentFile != null && currentFile != relbase) {
                    pathParts += currentFile.name
                    currentFile = currentFile.parentFile
                }
                pathParts.reverse().join('/')
            }
        }
        File a = new File('foo')
        File b = new File(new File(a, 'bar'), 'baz')
        assert new Tool().relativePath(a,b) == 'bar/baz'
        '''
    }

    void testCompileStaticTwiceShouldNotBeAProblemUsingCustomizer() {
        assertScript '''import groovy.transform.CompileStatic
        @CompileStatic
        class Tool {
            @CompileStatic // annotated too, even if class is already annotated
            String relativePath(File relbase, File file) {
                def pathParts = []
                def currentFile = file
                while (currentFile != null && currentFile != relbase) {
                    pathParts += currentFile.name
                    currentFile = currentFile.parentFile
                }
                pathParts.reverse().join('/')
            }
        }
        File a = new File('foo')
        File b = new File(new File(a, 'bar'), 'baz')
        assert new Tool().relativePath(a,b) == 'bar/baz'
        '''
    }

    // GROOVY-5613
    void testNullSafeAssignment() {
        assertScript '''
        class A {
            int x = -1
        }
        A a = new A()
        @ASTTest(phase=INSTRUCTION_SELECTION, value={
            assert node.getNodeMetaData(INFERRED_TYPE) == Integer_TYPE
        })
        def x = a?.x
        '''
    }

    void testNullSafeAssignmentWithLong() {
        assertScript '''
        class A {
            long x = -1
        }
        A a = new A()
        @ASTTest(phase=INSTRUCTION_SELECTION, value={
            assert node.getNodeMetaData(INFERRED_TYPE) == Long_TYPE
        })
        def x = a?.x
        '''
    }

    void testNullSafeAssignmentWithChar() {
        assertScript '''
        class A {
            char x = 'a'
        }
        A a = new A()
        @ASTTest(phase=INSTRUCTION_SELECTION, value={
            assert node.getNodeMetaData(INFERRED_TYPE) == Character_TYPE
        })
        def x = a?.x
        assert x == 'a'
        '''
    }

    void testCallStaticallyImportedMethodWithNullSafeArgument() {
        assertScript '''import static java.lang.Math.abs
        class A {
            int x = -1
        }
        def a = new A()
        def x = a?.x
        assert abs(a?.x) == 1
        '''
    }

    void testClosureAsInterfaceArgument() {
        assertScript '''
                Closure c = { Integer x, Integer y -> x <=> y }
                def list = [ 3,1,5,2,4 ]
                assert ((Collection)list).sort(c) == [1,2,3,4,5]
            '''
    }

    void testInferredTypeForInteger() {
        assertScript '''
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                assert node.getNodeMetaData(INFERRED_TYPE) == Integer_TYPE
            })
            Integer x = 1
        '''
    }

    // GROOVY-5671
    void testPostfixIncPrimitiveInteger() {
        assertScript '''
            int x = 0
            x++
            x++
            assert x == 2
            assert x++ == 2
            assert x == 3
        '''
    }

    void testPostfixIncInteger() {
        assertScript '''
                Integer x = 0
                x++
                x++
                assert x == 2
                assert x++ == 2
                assert x == 3
            '''
    }

    void testPostfixDecInt() {
        assertScript '''
                int x = 0
                x--
                x--
                assert x == -2
                assert x-- == -2
                assert x == -3
            '''
    }

    void testPostfixDecInteger() {
        assertScript '''
                Integer x = 0
                x--
                x--
                assert x == -2
                assert x-- == -2
                assert x == -3
            '''
    }

    void testPrefixIncPrimitiveInteger() {
        assertScript '''
            int x = 0
            ++x
            ++x
            assert x == 2
            assert ++x == 3
            assert x == 3
        '''
    }

    void testPrefixIncInteger() {
        assertScript '''
                Integer x = 0
                ++x
                ++x
                assert x == 2
                assert ++x == 3
                assert x == 3
            '''
    }

    void testPrefixDecInt() {
        assertScript '''
                int x = 0
                --x
                --x
                assert --x == -3
                assert x == -3
            '''
    }

    void testPrefixDecInteger() {
        assertScript '''
                Integer x = 0
                --x
                --x
                assert --x == -3
                assert x == -3
            '''
    }

    void testShouldSkipSpreadOperator() {
        new GroovyShell().evaluate '''import groovy.transform.TypeCheckingMode
            import groovy.transform.CompileStatic

            @CompileStatic // top level must be @CS
            class Foo {
                @CompileStatic(TypeCheckingMode.SKIP)
                static void foo(fun, args) {
                    new Runnable() { // create an anonymous class which should *not* be visited
                        void run() {
                            fun(*args) // spread operator is disallowed with STC/SC, but SKIP should prevent from an error
                        }
                    }
                }
            }
            new Foo()
        '''
    }

    // GROOVY-5672
    void testTypeCheckedPlusCompileStatic() {
        new GroovyShell().evaluate '''import groovy.transform.CompileStatic
        import groovy.transform.TypeChecked

        @TypeChecked
        @CompileStatic
        class SampleClass {
            def a = "some string"
            def b = a.toString()
        }
        new SampleClass()
        '''
    }

    void testSubclassShouldNotThrowArrayIndexOutOfBoundsException() {
        assertScript '''
            // The error only shows up if the subclass is compiled *before* the superclass
            class Subclass extends Z {
                public Subclass(double x, double y, double z) {
                    super(x,y,z)
                }
            }
            class Z {
               double x, y, z

                public Z(double x, double y, double z) { this.x = x; this.y = y; this.z = z }

                public Z negative() { return new Z(-x, -y, -z) }
            }
            new Subclass(0,0,0)
        '''
    }

    void testIncrementOperatorOnInt() {
        assertScript '''
                int incInt(int n) {
                    def result = n
                    ++result
                    result++
                    return result
                }
                assert  incInt(5) == 7'''
    }

    void testIncrementOperatorOnShort() {
        assertScript '''
                short incInt(short n) {
                    def result = n
                    ++result
                    result++
                    return result
                }
                assert  incInt((short)5) == 7'''
    }

    void testIncrementOperatorOnByte() {
        assertScript '''
                byte incInt(byte n) {
                    def result = n
                    ++result
                    result++
                    return result
                }
                assert  incInt((byte)5) == 7'''
    }

    void testIncrementOperatorOnLong() {
        assertScript '''
                long incInt(long n) {
                    def result = n
                    ++result
                    result++
                    return result
                }
                assert  incInt(5) == 7'''
    }

    void testIncrementOperatorOnFloat() {
        assertScript '''
                float incInt(float n) {
                    def result = n
                    ++result
                    result++
                    return result
                }
                assert  incInt(5) == 7'''
    }

    void testIncrementOperatorOnDouble() {
        assertScript '''
                double incInt(double n) {
                    def result = n
                    ++result
                    result++
                    return result
                }
                assert  incInt(5) == 7'''
    }

    void testIncrementOperatorOnChar() {
        assertScript '''
                char incInt(char n) {
                    def result = n
                    ++result
                    result++
                    return result
                }
                assert  incInt((char)'a') == (char)('c')'''
    }

    void testIncrementField() {
        assertScript '''
            class Foo {
                int x
            }
            def f = new Foo()
            f.x++
            assert f.x == 1
            assert f.x++ == 1
            assert f.x == 2
        '''
    }

    // GROOVY-5789
    void testLoopWithIncrement() {
        assertScript '''
        int execute() {
            // using a list, so that if the loop is endless, the test eventually fails with OOM
            List<Integer> list = new LinkedList<Integer>()
            for (def i = 0; i < 4; ++i) { println i; list << i }
            list.size()
        }
        assert execute() == 4
        '''
    }

    // GROOVY-5800
    void testInOperator() {
        assertScript '''
            boolean m( Integer i ) {
              i in [ 1, 2, 3 ]
            }
            assert m(1) == true
            assert m(4) == false
            '''
    }

    // GROOVY-5814
    void testCompileStaticImmutable() {
        assertScript '''
            import groovy.transform.*

            @Immutable
            class Test {
              int a
              String b
            }

            new Test( 1, 'tim' )
        '''
    }

    // GROOVY-5738
    void testAccessFieldFromGStringAfterInstanceOf() {
        new GroovyShell().evaluate '''
            class Greeting { String who }

            @groovy.transform.CompileStatic
            class GreetingActor {

              def receive = {
                if(it instanceof Greeting) {
                    println "Hello ${it.who}"
                }
              }

            }
            new GreetingActor().receive(new Greeting(who:'cedric'))
        '''
    }

    // GROOVY-5738
    void testAccessMethodFromGStringAfterInstanceOf() {
        new GroovyShell().evaluate '''
            class Greeting {
                String who
                String whoAmI() { who }
            }

            @groovy.transform.CompileStatic
            class GreetingActor {

              def receive = {
                if(it instanceof Greeting) {
                    println "Hello ${it.whoAmI()}"
                }
              }

            }
            new GreetingActor().receive(new Greeting(who:'cedric'))
        '''
    }

    // GROOVY-5804
    void testNegateSharedBooleanInClosure() {
        assertScript '''
                boolean x = false
                def cl = {
                    if (!x) {
                        assert true
                    } else {
                        assert false
                    }
                }
                cl()
            '''
    }

    void testCallClosureInInnerClass() {
        assertScript '''
            class A {
                static class B { // bug doesn't occur if not wrapped into an inner class
                    static void foo() {
                        def cl = { -> println 'ok' }
                        cl()
                    }
                }
            }
            A.B.foo()
        '''
    }

    // GROOVY-5890
    void testIsCaseWithClassLiteral() {
        assertScript '''
            assert 'a' in String
        '''
    }

    // GROOVY-5887
    void testSpreadCallWithArray() {
        assertScript '''
            def list = 'a,b,c'.split(/,/)*.trim()
            assert list == ['a','b','c']
        '''
    }

    // GROOVY-5919
    void testPrivateAccessorsWithSubClass() {
        assertScript '''
            class Top {
                private int foo = 666
                private class InnerTop {
                    int foo() { foo }
                }
            }
            class Bottom extends Top {
                private int bar = 666
                private class InnerBottom {
                    int bar() { bar } // name clash for fpaccess$0
                }
            }
            new Bottom()
        '''
    }

    void testSuperMethodCallInSkippedSection() {
        assertScript '''import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
            class Top {
                public int foo() { 123 }
            }
            class Bottom extends Top {
                @CompileStatic(TypeCheckingMode.SKIP)
                public int bar() {
                    foo()
                }
            }
            def obj = new Bottom()
            assert obj.bar() == 123
        '''
    }

    void testArrayGetOnObject() {
        shouldFailWithMessages '''
            def foo(Object o) {
                o[0]
            }
        ''', 'Cannot find matching method java.lang.Object#getAt(int)'
    }

    void testStaticCompileWithPattern() {
        shouldFailWithMessages '''
            def fieldMatcher = '[x=1] [a=b] [foo=bar]' =~ ~"\\\\[([^=\\\\[]+)=([^\\\\]]+)\\\\]"
            assert fieldMatcher instanceof java.util.regex.Matcher
            @ASTTest(phase=INSTRUCTION_SELECTION, value = {
                assert node.getNodeMetaData(INFERRED_TYPE) == OBJECT_TYPE
            })
            def value = fieldMatcher[0]
            // should not pass
            def str = value[0]
        ''', 'Cannot find matching method java.lang.Object#getAt(int)'
    }

    void testChainedNullSafePropertyOnMap() {
        assertScript '''
        Map<String, Map<String,Map<String,Integer>>> m=[:]
        // this is ok
        assert m?.a == null
        assert m?.a?.b == null
        assert m?.a?.b?.c == null
        assert m?.a?.b?.c?.intValue() == null
        '''
    }

    void testNullSafePropertyOnList() {
        assertScript '''
        List<Class> classes = null
        // this is ok
        assert classes?.name == null
        '''
    }

    // GROOVY-6061
    void testShouldCallPutAt() {
        assertScript '''
            def swapUseStatic(List<Integer> a, int i, int j) {
                int temp = a[i]
                a[i] = a[j]  //BUG HERE, a[i] = ... doesn't call "putAt"
                a[j] = temp
            }
            def list = [1,2]
            swapUseStatic(list, 0, 1)
            assert list == [2,1]
        '''
    }

    // GROOVY-6101
    void testShouldNotGenerateInvalidClassWithNullSafeInvocationOnMethodReturningPrimitiveType() {
        assertScript '''
        class Piece {
            int x() { 333 }
        }

        void foo() {
            Piece[] pieces = [new Piece(), null] as Piece[]
            int sum = 0
            for (int i=0;i<pieces.length;i++) {
                if (pieces[i]?.x()) {
                    sum += pieces[i].x()
                }
            }
            assert sum == 333
        }
        foo()
        '''
    }

    // GROOVY-6101 fix side effect
    void testMakeSureOptimizationKeepsSideEffect() {
        assertScript '''
            class Foo {
                boolean hasSideEffect = false
                int x() { hasSideEffect=true; 333 }
            }
            Foo foo = new Foo()
            if (foo.x()==null) {
                println 'ok'
            }
            assert foo.hasSideEffect
         '''
    }

    void testShouldNotThrowNPEBecauseLHSIsNull() {
        assertScript '''
            class A {
                int foo() { 123 }
            }
            A a = null
            def bar = a?.foo() == 123 // will evaluate to false
            assert bar == false
        '''
    }

    // GROOVY-5921
    void testShouldHandleLongArray() {
        assertScript '''
            class Groovy5921 {
              long[] data = [1L]

              void doSomething() {
                data[ 0 ] |= 0x7ffffffl
              }
            }

            def b = new Groovy5921()
            b.doSomething()

        '''
    }

    void testShouldHandleLongArrayWithUnboxing() {
        assertScript '''
            class Groovy5921 {
              long[] data = [1L]

              void doSomething() {
                data[ 0 ] |= Long.valueOf(0x7ffffffl)
              }
            }

            def b = new Groovy5921()
            b.doSomething()

        '''
    }

    void testShouldHandleBoxedLongArray() {
        assertScript '''
            class Groovy5921 {
              Long[] data = [1L]

              void doSomething() {
                data[ 0 ] |= Long.valueOf(0x7ffffffl)
              }
            }

            def b = new Groovy5921()
            b.doSomething()

        '''
    }

    void testShouldHandleBoxedLongArrayWithBoxing() {
        assertScript '''
            class Groovy5921 {
              Long[] data = [1L]

              void doSomething() {
                data[ 0 ] |= 0x7ffffffl
              }
            }

            def b = new Groovy5921()
            b.doSomething()

        '''
    }

    // GROOVY-6113
    void testCallObjectVargsMethodWithPrimitiveIntConstant() {
        try {
            assertScript '''
                int sum(Object... elems) {
                     (Integer)elems.toList().sum()
                }
                int x = sum(Closure.DELEGATE_FIRST)
                assert x == Closure.DELEGATE_FIRST
            '''
        } finally {
//            println astTrees
        }
    }

    // GROOVY-6095
    void testServletError() {
        def shell = new GroovyShell()
        shell.evaluate '''
            @Grab('javax.servlet:javax.servlet-api:3.0.1')
            import groovy.transform.CompileStatic

            import javax.servlet.ServletContext
            import javax.servlet.ServletRegistration

            /**

             * author: Richard Vowles - http://gplus.to/RichardVowles
             */
            @CompileStatic
            class ServletExample {

              public void myMethod(ServletContext ctx) {
                ctx.getServletRegistrations().each { String name, ServletRegistration sr ->
                  println name
                }

              }
            }
            new ServletExample()
        '''
    }

    // GROOVY-6137
    void testIsCaseShouldBeNullSafe() {
        assertScript '''
        def isCaseNullCS(a, b) {
            a in b
        }
        assert isCaseNullCS(1, null) == false
        assert isCaseNullCS(null, 1) == false
        assert isCaseNullCS(1,1) == true
        assert isCaseNullCS(1,2) == false
        assert isCaseNullCS(2,1) == false
        '''
    }

    // GROOVY-6242
    void testGetAtBigInt() {
        assertScript '''
class Sequence {
    public Sequence() {}
    static BigInteger getAt(final int index) { 1G }
    static BigInteger getAt(final BigInteger index) { getAt(index as int) }
}

class Iterator implements java.util.Iterator {
    private BigInteger currentIndex = 0G
    private final Sequence sequence
    Iterator(final Sequence s) { sequence = s }
    boolean hasNext() { return true }
    @ASTTest(phase=INSTRUCTION_SELECTION,value={
        def expr = node.code.statements[0].expression
        def indexType = expr.rightExpression.getNodeMetaData(INFERRED_TYPE)
        assert indexType == make(BigInteger)
    })
    BigInteger next() { sequence[currentIndex++] }
    void remove() { throw new UnsupportedOperationException() }
}

def it = new Iterator(new Sequence())
assert it.next() == 1G
'''
    }

    // GROOVY-6243
    void testSwapUsingMultipleAssignment() {
        assertScript '''
        def swap(result,next) {
            print "($result,$next) -> "
            (result, next) =  [next, result]
            println "($result,$next)"
            [result, next]
        }

        assert swap(0,1) == [1,0]
        assert swap('a','b') == ['b','a']
        assert swap('a', 1) == [1, 'a']
        def o1 = new Object()
        def o2 = new Date()
        assert swap(o1,o2) == [o2, o1]
        '''
    }

    // GROOVY-5882
    void testMod() {
        assertScript """
            int foo(Map<Integer, Object> markers, int i) {
                int res = 0
                for (e in markers.entrySet()) {
                    res += i % e.key
                }
                return res
            }
            assert foo([(1):null,(2):null,(3):null],2)==2
        """

        assertScript """
            int foo(Map<Integer, Object> markers, int i) {
                int res = 0
                for (e in markers.entrySet()) {
                    int intKey = e.key
                    res += i % intKey
                }
                return res
            }
            assert foo([(1):null,(2):null,(3):null],2)==2
        """
        assertScript """
            int foo(Map<Integer, Object> markers, int i) {
                int res = 0
                for (e in markers.entrySet()) {
                    res += i % e.key.intValue()
                }
                return res
            }
            assert foo([(1):null,(2):null,(3):null],2)==2
        """
    }

    void testSuperCallShouldBeDirect() {
        try {
            assertScript '''
                class TwoException extends Exception {
                    @ASTTest(phase=INSTRUCTION_SELECTION,value={
                        def superCall = node.code.statements[0].expression
                        assert superCall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)!=null
                    })
                    public TwoException(Throwable t) {
                        super(t)
                    }
                }
                def e = new TwoException(null) // will not throw an exception
            '''
        } finally {
            assert !astTrees.TwoException.contains('selectConstructorAndTransformArguments')
        }
    }

    void testNullSafeOperatorShouldNotCallMethodTwice() {
        assertScript '''
            import java.util.concurrent.atomic.AtomicLong

            class Sequencer {
              private final AtomicLong sequenceNumber = new AtomicLong(0)

              public Long getNext() {
                return sequenceNumber.getAndIncrement()
              }
            }

            final seq = new Sequencer()
            (1..5).each {
              println seq.next?.longValue()
            }
            assert seq.next == 5
'''
    }

    void testNullSafeOperatorShouldNotCallMethodTwiceWithPrimitive() {
        assertScript '''
            import java.util.concurrent.atomic.AtomicLong

            class Sequencer {
              private final AtomicLong sequenceNumber = new AtomicLong(0)

              public long getNext() {
                sequenceNumber.getAndIncrement()
              }
            }

            final seq = new Sequencer()
            (1..5).each {
              println seq.next?.longValue()
            }
            assert seq.next == 5
'''
    }

    void testNullSafeOperatorShouldNotCallMethodTwice1Arg() {
        assertScript '''
            import java.util.concurrent.atomic.AtomicLong

            class Sequencer {
              private final AtomicLong sequenceNumber = new AtomicLong(0)

              public Long getNext(int factor) {
                factor*sequenceNumber.getAndIncrement()
              }
            }

            final seq = new Sequencer()
            (1..5).each {
              println seq.getNext(2)?.longValue()
            }
            assert seq.getNext(2) == 10
'''
    }

    void testNullSafeOperatorShouldNotCallMethodTwiceWithPrimitive1Arg() {
        assertScript '''
            import java.util.concurrent.atomic.AtomicLong

            class Sequencer {
              private final AtomicLong sequenceNumber = new AtomicLong(0)

              public long getNext(int factor) {
                factor*sequenceNumber.getAndIncrement()
              }
            }

            final seq = new Sequencer()
            (1..5).each {
              println seq.getNext(2)?.longValue()
            }
            assert seq.getNext(2) == 10
'''
    }

    void testShouldAllowSubscriptOperatorOnSet() {
        assertScript '''
            def map = new LinkedHashMap<>([a:1,b:2])
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                def ift = node.getNodeMetaData(INFERRED_TYPE)
                assert ift == make(Set)
                assert ift.isUsingGenerics()
                assert ift.genericsTypes[0].type==STRING_TYPE
            })
            def set = map.keySet()
            def key = set[0]
            assert key=='a'
        '''
        assertScript '''
            def map = new LinkedHashMap([a:1,b:2])
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                def ift = node.getNodeMetaData(INFERRED_TYPE)
                assert ift == make(Set)
                assert ift.isUsingGenerics()
                assert ift.genericsTypes[0].name=='K'
            })
            def set = map.keySet()
            def key = set[0]
            assert key=='a'
        '''
    }

    // GROOVY-6552
    void testShouldNotThrowClassCastException() {
        assertScript '''import java.util.concurrent.Callable

    String text(Class clazz) {
        new Callable<String>() {
            String call() throws Exception {
                new Callable<String>() {
                    String call() throws Exception {
                        clazz.getName()
                    }
                }.call()
            }
        }.call()
    }

    assert text(String) == 'java.lang.String'
    '''
    }

    // GROOVY-6851
    void testShouldNotThrowNPEIfElvisOperatorIsUsedInDefaultArgumentValue() {
        assertScript '''import org.codehaus.groovy.ast.expr.MethodCallExpression

class GrailsHomeWorkspaceReader {
    @ASTTest(phase=INSTRUCTION_SELECTION,value={
        def defaultValue = node.parameters[0].initialExpression
        assert defaultValue instanceof MethodCallExpression
        def target = defaultValue.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)
        assert target != null
    })
    GrailsHomeWorkspaceReader(String grailsHome = System.getProperty('grails.home')) {
    }
}
new GrailsHomeWorkspaceReader()
'''
        assertScript '''
class GrailsHomeWorkspaceReader {
    GrailsHomeWorkspaceReader(String grailsHome = System.getProperty('grails.home') ?: System.getenv('GRAILS_HOME')) {
    }
}
new GrailsHomeWorkspaceReader()
'''
    }

    // GROOVY-6342
    void testShouldNotThrowNPEIfElvisOperatorIsUsedInsideTernary() {
        assertScript '''class Inner {
    int somestuff
}
Inner inner = null
int someInt = inner?.somestuff ?: 0
println someInt

'''
    }

    void testAccessOuterClassMethodFromInnerClassConstructor() {
        assertScript '''
    class Parent {
        String str
        Parent(String s) { str = s }
    }
    class Outer {
        String a

        private class Inner extends Parent {
           Inner() { super(getA()) }
        }

        String test() { new Inner().str }
    }
    def o = new Outer(a:'ok')
    assert o.test() == 'ok'
    '''
    }

    void testAccessOuterClassMethodFromInnerClassConstructorUsingExplicitOuterThis() {
        assertScript '''
    class Parent {
        String str
        Parent(String s) { str = s }
    }
    class Outer {
        String a

        private class Inner extends Parent {
           Inner() { super(Outer.this.getA()) }
        }

        String test() { new Inner().str }
    }
    def o = new Outer(a:'ok')
    assert o.test() == 'ok'
    '''
    }

    void testAccessOuterClassMethodFromInnerClassConstructorUsingExplicitOuterThisAndProperty() {
        assertScript '''
    class Parent {
        String str
        Parent(String s) { str = s }
    }
    class Outer {
        String a

        private class Inner extends Parent {
           Inner() { super(Outer.this.a) }
        }

        String test() { new Inner().str }
    }
    def o = new Outer(a:'ok')
    assert o.test() == 'ok'
    '''
    }

    void testAccessOuterClassStaticMethodFromInnerClassConstructor() {
        assertScript '''
    class Parent {
        String str
        Parent(String s) { str = s }
    }
    class Outer {
        static String a

        private class Inner extends Parent {
           Inner() { super(getA()) }
        }

        String test() { new Inner().str }
    }
    def o = new Outer()
    Outer.a = 'ok'
    assert o.test() == 'ok'
    '''
    }

    void testStaticMethodFromInnerClassConstructor() {
        assertScript '''
    class Parent {
        String str
        Parent(String s) { str = s }
    }
    class Outer {
        private class Inner extends Parent {
           static String a = 'ok'
           Inner() { super(getA()) }
        }

        String test() { new Inner().str }
    }
    def o = new Outer()
    assert o.test() == 'ok'
    '''
    }

    // GROOVY-6876
    void testSetterOfPrimitiveType() {
        assertScript '''
            class Foo {
                long bar
                def method() {
                    setBar(-1L)
                    bar
                }
            }
            assert new Foo().method() == -1L
            '''

        assertScript '''
            class Foo {
                long bar
                def method(Long v) {
                    setBar(v)
                    bar
                }
            }
            assert new Foo().method(-1L) == -1L
        '''

        assertScript '''
            class Foo {
                long rankOrderingOrId
                void setRankOrderingOrId(long rankOrderingOrId) {
                    this.rankOrderingOrId = rankOrderingOrId < 0 ? -1 : rankOrderingOrId
                }
            }
            def f = new Foo()
            f.setRankOrderingOrId(1L)
            assert f.getRankOrderingOrId() == 1L
            assert f.rankOrderingOrId == 1L
            f.rankOrderingOrId = 2L
            assert f.getRankOrderingOrId() == 2L
            assert f.rankOrderingOrId == 2L
        '''
    }

    // GROOVY-6921
    void testSubscriptOnClosureSharedVariable() {
        assertScript '''
            def versionRanges = [['1.7', 3]]
            def versions = versionRanges.collect { versionRange -> (0..versionRange[1]).collect { "${versionRange[0]}.${it}" } }.flatten()
            assert versions == ['1.7.0','1.7.1','1.7.2','1.7.3']
        '''
    }

    // GROOVY-6924
    void testShouldNotThrowIncompatibleClassChangeError() {
        try {
            assertScript '''import org.codehaus.groovy.classgen.asm.sc.Groovy6924Support
            class Test {
                static void foo() {
                    Groovy6924Support bean = new Groovy6924Support()
                    bean.with {
                        foo = 'foo'
                        bar = 'bar'
                    }
                    String val = "$bean.foo and $bean.bar"
                    assert val == 'foo and bar'
                }
            }
            Test.foo()
        '''
        } finally {
            assert astTrees['Test$_foo_closure1'][1].contains('INVOKEVIRTUAL org/codehaus/groovy/classgen/asm/sc/Groovy6924Support.setFoo (Ljava/lang/String;)V')
        }
    }

    // GROOVY-7381
    void testNonVoidSetterCalls(){
        assertScript '''
            class Foo {
                int num
                String name

                Foo setNum(int num){
                    this.num = num
                    this
                }

                Foo setName(String name){
                    this.name = name
                    this
                }
            }
            Foo foo = new Foo().setNum(1).setName('fluent')
            assert foo.num == 1
            assert foo.name == 'fluent'
        '''
    }

    // GROOVY-7610
    void testNullSafeIsCallConditionShouldNotThrowVerifyError() {
        assertScript '''
            class A {
                void ifCondition(Object x, Object y) {
                    if (x?.is(y))
                        return
                }

                void ternaryCondition(Object x, Object y) {
                    x?.is(y) ? 'foo' : 'bar'
                }
            }
            new A()
        '''
    }

    // GROOVY-7631
    void testPrimitiveNotEqualNullShouldReturnTrue() {
        assertScript '''
            assert false != null
            assert true != null
            assert (byte) 1 != null
            assert (short) 1 != null
            assert 0 != null
            assert 1 != null
            assert 1L != null
            assert 1f != null
            assert 1d != null
            assert (char) 1 != null
        '''
    }

    // GROOVY-7639
    void testComparisonOfPrimitiveWithNullSafePrimitivePropertyExpression() {
        assertScript '''
            class Foo {
                int bar
            }
            Foo foo = null
            assert !(foo?.bar == 7)
            assert !(foo?.bar > 7)
            assert foo?.bar < 7
        '''
    }

    // GROOVY-7841
    void testAssertionOfNonZeroPrimitiveEdgeCases() {
        assertScript '''
            assert 4294967296
            assert 0.1f
            assert 0.1d
        '''
    }

    // GROOVY-7784
    void testWithSamAndVarArgs() {
        assertScript '''
            class Foo {
                static foo(Integer x, Iterable y, String... z) { [*y.toList(), *z].join('-') }
            }

            class Groovy7784 {
                static emptyVarArgs() { Foo.foo(42, { ['foo', 'bar'].iterator() }) }
                static singleVarArgs() { Foo.foo(42, { ['foo', 'bar'].iterator() }, 'baz') }
                static multiVarArgs() { Foo.foo(42, { ['foo', 'bar'].iterator() }, 'baz1', 'baz2') }
            }

            assert Groovy7784.emptyVarArgs() == 'foo-bar'
            assert Groovy7784.singleVarArgs() == 'foo-bar-baz'
            assert Groovy7784.multiVarArgs() == 'foo-bar-baz1-baz2'
        '''
    }
}

