/*
 *  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 groovy.transform.stc

import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.tools.WideningCategories
import org.codehaus.groovy.classgen.GeneratorContext
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.control.customizers.CompilationCustomizer
import org.codehaus.groovy.transform.stc.StaticTypesMarker
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

/**
 * Unit tests for static type checking : type inference.
 */
class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {

    @Test
    void testStringToInteger() {
        assertScript '''
            def name = "123" // we want type inference
            name.toInteger() // toInteger() is defined by DGM
        '''
    }

    // GROOVY-9935
    @ParameterizedTest
    @ValueSource(strings=['def','int','Integer','BigInteger'])
    void testIntegerToNumber(String type) {
        assertScript """
            Number f() {
                $type n = 10
                return n
            }
            assert f() == 10
        """
    }

    @Test
    void testGStringMethods() {
        assertScript '''
            def myname = 'Cedric'
            "My upper case name is ${myname.toUpperCase()}"
            println "My upper case name is ${myname}".toUpperCase()
        '''
    }

    @Test
    void testDynamicMethodWithinTypeCheckedClass() {
        assertScript '''
            import groovy.transform.*

            class C {
                String m(String s) {
                    generateMarkup(s.toUpperCase())
                }

                // MarkupBuilder is dynamic so skip type-checking
                @TypeChecked(TypeCheckingMode.SKIP)
                String generateMarkup(String s) {
                    def sw = new StringWriter()
                    def mb = new groovy.xml.MarkupBuilder(sw)
                    mb.html {
                        body {
                            div s
                        }
                    }
                    sw.toString()
                }
            }

            def c = new C()
            def xml = c.m('x')
            // TODO: check XML
        '''
    }

    @Test
    void testInstanceOf1() {
        assertScript '''
            Object o
            if (o instanceof String) {
                o.toUpperCase()
            }
        '''
    }

    @Test
    void testInstanceOf2() {
        assertScript '''
            Object o
            if (o instanceof String) {
                if (true) {
                    o.toUpperCase()
                }
            }
        '''
    }

    @Test
    void testInstanceOf3() {
        shouldFailWithMessages '''
            Object o
            if (o instanceof String) {
               o.toUpperCase()
            }
            o.toUpperCase() // ensure that type information is reset
        ''',
        'Cannot find matching method java.lang.Object#toUpperCase()'
    }

    @Test
    void testInstanceOf4() {
        shouldFailWithMessages '''
            Object o
            if (o instanceof String) {
                o.toUpperCase()
            } else {
                o.toUpperCase() // ensure that type information is reset
            }
        ''',
        'Cannot find matching method java.lang.Object#toUpperCase()'
    }

    @Test
    void testInstanceOf5() {
        assertScript '''
            class A {
                void bar() {
                }
            }
            class B {
                void bar() {
                }
            }
            void test(Object o) {
                if (o instanceof A) {
                    o.bar()
                }
                if (o instanceof B) {
                    o.bar()
                }
            }
            test(new A())
            test(new B())
        '''
    }

    // GROOVY-9953
    @Test
    void testInstanceOf6() {
        assertScript '''
            class A {
            }
            A test(Object o) {
                if (o instanceof A) {
                    def a = o
                    return a
                } else {
                    new A()
                }
            }
            new A().with { assert test(it) === it }
        '''
    }

    // GROOVY-8828
    @Test
    void testInstanceOf7() {
        assertScript '''
            interface Foo {
            }
            interface Bar {
                String name()
            }
            Map<String, Foo> map = [:]
            map.values().each { foo ->
                if (foo instanceof Bar) {
                    String name = foo.name() // method available through Bar
                    map.put(name, foo)       // second parameter expects Foo
                }
            }
        '''
    }

    // GROOVY-9454
    @Test
    void testInstanceOf8() {
        assertScript '''
            interface Face {
            }
            class Impl implements Face {
                String something
            }
            class Task<R extends Face> implements java.util.concurrent.Callable<String> {
                R request
                @Override
                String call() {
                    if (request instanceof Impl) {
                        def thing = request.something // No such property: something for class: R
                        def lower = thing.toLowerCase()
                    } else {
                        // ...
                        return null
                    }
                }
            }
            def task = new Task<Impl>(request: new Impl(something: 'Hello World'))
            assert task.call() == 'hello world'
        '''
    }

    // GROOVY-10667
    @Test
    void testInstanceOf9() {
        assertScript '''
            trait Tagged {
                String tag
            }
            class TaggedException extends Exception implements Tagged {
            }
            static void doSomething1(Exception e) {
                if (e instanceof Tagged) {
                    assert e.tag == 'Test'
                    doSomething2(e) // Cannot find matching method #doSomething2(Tagged)
                }
            }
            static void doSomething2(Exception e) {
            }
            doSomething1(new TaggedException(tag:'Test'))
        '''
    }

    // GROOVY-7971
    @Test
    void testInstanceOf10() {
        assertScript '''
            import groovy.json.JsonOutput
            def test(value) {
                def out = new StringBuilder()
                def isString = value.class == String
                if (isString || value instanceof Map) {
                    out.append(JsonOutput.toJson(value))
                }
                return out.toString()
            }
            def string = test('two')
            assert string == '"two"'
        '''

        assertScript '''
            abstract class Foo {
                abstract boolean isBaz()
            }
            class Bar extends Foo {
                final boolean baz = false
            }
            class Baz extends Foo {
                final boolean baz = true
            }
            void test(Foo foo) {
                if (foo instanceof Bar || foo.isBaz()) {
                    foo.toString()
                }
            }
            test(new Bar())
            test(new Baz())
            test(new Foo(){ boolean isBaz() { false } })
        '''
    }

    // GROOVY-8337
    @Test
    void testInstanceOf11() {
        assertScript '''
            class C {
                private Number n
                BigDecimal m() {
                    (n == null || n instanceof BigDecimal)
                        ? n : new BigDecimal(n.toString())
                }
            }
            assert new C().m() == null
        '''

        assertScript '''
            class C {
                private Number n = new BigDecimal('1.23')
                BigDecimal m() {
                    (n == null || n instanceof BigDecimal)
                        ? n : new BigDecimal(n.toString())
                }
            }
            assert new C().m() == 1.23G
        '''
    }

    // GROOVY-10096
    @Test
    void testInstanceOf12() {
        shouldFailWithMessages '''
            class Foo {
                void foo() {
                }
            }
            class Bar extends Foo {
                void bar() {
                }
            }
            static Bar baz(Foo foo) {
                (false || foo instanceof Bar) ? foo : new Bar()
            }
        ''',
        'Cannot return value of type Foo for method returning Bar'
    }

    @Test
    void testInstanceOf13() {
        assertScript '''
            "".with { x ->
                if (x instanceof String) {
                    x.toUpperCase()
                }
            }
        '''
    }

    // GROOVY-11007
    @Test
    void testInstanceOf14() {
        assertScript '''
            interface I {
                CharSequence getCharSequence()
            }
            void accept(CharSequence cs) {
            }
            void test(I i) {
                i.with {
                    if (charSequence instanceof String) {
                        charSequence.toUpperCase()
                        accept(charSequence)
                    }
                }
            }
            test({ -> 'works' } as I)
        '''
    }

    // GROOVY-11290
    @Test
    void testInstanceOf15() {
        assertScript '''
            def test(List<String> list) {
                if (list instanceof List) {
                    (list*.toLowerCase()).join()
                }
            }
            String result = test(['foo', 'bar'])
            assert result == 'foobar'
        '''
    }

    // GROOVY-11815
    @Test
    void testInstanceOf16() {
        assertScript '''
            def c = true ? new ArrayDeque() : new Stack()
            if (c instanceof Deque) {
                c.addFirst(1)
            //  ^ (AbstractCollection<Object> & Serializable & ... & Deque)
            }
        '''
        shouldFailWithMessages '''
            def c = true ? new ArrayDeque<String>() : new Stack<String>()
            if (c instanceof Serializable) {
                c.add(1)
            //  ^ (AbstractCollection<String> & Serializable & ... & Deque)
            }
        ''',
        'Cannot call (java.util.AbstractCollection','#add(java.lang.String) with arguments [int]'
    }

    // GROOVY-11864
    @Test
    void testInstanceOf17() {
        assertScript '''
            boolean enabled() {}
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                def expr = lookup('x').get(0).expression
                def type = expr.getNodeMetaData(INFERRED_TYPE)
                assert type.name == 'java.lang.Number'
            })
            def test(Number n) {
                if (enabled()) {
                    if (n instanceof Long) return n
                    throw new IllegalArgumentException()
                }
            x:  n
            }
            assert test(42) == 42
        '''

        shouldFailWithMessages '''
            void test(Integer i) {
                if (i instanceof Long) {
                }
            }
        ''',
        'Incompatible instanceof types: java.lang.Integer and java.lang.Long'
    }

    // GROOVY-7971: nested && within || — method calls on narrowed types within
    // each && branch should work; the || produces a union for the body
    @Test
    void testInstanceOf18() {
        assertScript '''
            int test(Object x) {
                if (x instanceof String && x.length() > 0 || x instanceof List && x.size() > 0) {
                    return 1
                }
                return 0
            }
            assert test('hello') == 1
            assert test([1, 2, 3]) == 1
            assert test('') == 0
            assert test([]) == 0
            assert test(42) == 0
        '''
    }

    // GROOVY-7971: ternary with || instanceof in condition
    @Test
    void testInstanceOf19() {
        assertScript '''
            String test(Object x) {
                (x instanceof String || x instanceof Integer) ? x.toString() : 'other'
            }
            assert test('hi') == 'hi'
            assert test(42) == '42'
            assert test(3.14) == 'other'
        '''
    }

    // GROOVY-7971: negated || instanceof
    @Test
    void testInstanceOf20() {
        assertScript '''
            void test(Object x) {
                if (!(x instanceof String || x instanceof Integer)) {
                    assert x != null
                }
            }
            test('hello')
            test(42)
            test(3.14)
        '''
    }

    // GROOVY-7971: chained || with 3+ instanceof checks
    @Test
    void testInstanceOf21() {
        assertScript '''
            void test(Object x) {
                if (x instanceof String || x instanceof Integer || x instanceof List) {
                    assert "$x" != null // should be String|Integer|List
                }
            }
            test('hello')
            test(42)
            test([1, 2])
        '''
    }

    // GROOVY-7971: RHS of || should not see LHS instanceof narrowing
    @Test
    void testInstanceOf22() {
        assertScript '''
            void test(Number n) {
                if (n instanceof Integer || n.doubleValue() > 0) {
                    assert "$n" != null // n should be Integer|Number
                }
            }
            test(42)
            test(1.5)
        '''
    }

    // GROOVY-7971: closure shared variable with || instanceof
    @Test
    void testInstanceOf23() {
        assertScript '''
            void test(Object x) {
                if (x instanceof String || x instanceof Integer) {
                    def c = { -> x.toString() }
                    assert c() != null
                }
            }
            test('hello')
            test(42)
        '''
    }

    // GROOVY-11888: method resolution on union type — toString() is on Object
    // and should be found via (String|List) union
    @Test
    void testInstanceOf24() {
        assertScript '''
            void test(Object x) {
                if (x instanceof String || x instanceof List) {
                    assert x.toString() != null
                }
            }
            test('hello')
            test([1, 2])
        '''
    }

    // GROOVY-11889: negated || instanceof — re-check instanceof in else branch
    @Test
    void testInstanceOf25() {
        assertScript '''
            void test(Object x) {
                if (!(x instanceof String || x instanceof Integer)) {
                    assert x != null
                } else {
                    assert x instanceof String || x instanceof Integer
                }
            }
            test('hello')
            test(42)
            test(3.14)
        '''
    }

    // GROOVY-11888: method resolution on union type — intValue() is on Number
    // and should be found via (Integer|Number) union
    @Test
    void testInstanceOf26() {
        assertScript '''
            void test(Number n) {
                if (n instanceof Integer || n.intValue() > 0) {
                    assert n.intValue() >= 0
                }
            }
            test(42)
            test(1.5)
        '''
    }

    // GROOVY-11889: instanceof interface check on union type
    @Test
    void testInstanceOf27() {
        assertScript '''
            void test(Object x) {
                if (x instanceof String || x instanceof Integer) {
                    if (x instanceof Serializable) {
                        assert true
                    }
                }
            }
            test('hello')
            test(42)
        '''
    }

    // GROOVY-11889: union containing interface delegate
    @Test
    void testInstanceOf28() {
        assertScript '''
            void test(Object x) {
                if (x instanceof Serializable || x instanceof String) {
                    if (x instanceof String) {
                        assert true
                    }
                }
            }
            test('hello')
        '''
    }

    // GROOVY-11889: incompatible instanceof on union type should produce error
    @Test
    void testInstanceOf29() {
        shouldFailWithMessages '''
            void test(Object x) {
                if (x instanceof String || x instanceof Integer) {
                    if (x instanceof Long) {
                        // unreachable - neither String nor Integer is related to Long
                    }
                }
            }
        ''',
        'Incompatible instanceof types'
    }

    // GROOVY-5226
    @Test
    void testNestedInstanceOf1() {
        assertScript '''
            Object o = "foo"
            if (o instanceof Object) {
                if (o instanceof String) {
                    o.toUpperCase()
                }
            }
        '''
    }

    // GROOVY-5226
    @Test
    void testNestedInstanceOf2() {
        assertScript '''
            Object o = "foo"
            if (o instanceof String) {
                if (o instanceof Object) {
                    o.toUpperCase()
                }
            }
        '''
    }

    // GROOVY-11290
    @Test
    void testNestedInstanceOf3() {
        assertScript '''
            Object o = null
            if (o instanceof Closeable) {
                if (o instanceof Cloneable) {
                    o.close()
                }
            }
        '''
    }

    @Test
    void testNestedInstanceOf4() {
        assertScript '''
            Object o = [1,2] as Number[]
            if (o instanceof Object[]) {
                if (o instanceof Number[]) {
                    o[0].intValue()
                }
            }
        '''
    }

    @Test
    void testNestedInstanceOf5() {
        assertScript '''
            class A {
               int foo() { 1 }
            }
            class B {
               int bar() { 2 }
            }
            class C {
            }

            def test(o) {
                o instanceof A ? o.foo() : (o instanceof B ? o.bar() : 3)
            }
            assert test(new A()) == 1
            assert test(new B()) == 2
            assert test(new C()) == 3
        '''
    }

    @Test
    void testMultipleInstanceOf1() {
        assertScript '''
            class A {
                Float m() { 1 }
            }
            class B {
                Short m() { 3 }
            }

            int foo(o) {
                if (o instanceof A || o instanceof B) {
                    return o.m().compareTo(2)
                }
            }
            assert foo(new A()) < 0
            assert foo(new B()) > 0
        '''

        assertScript '''
            class A {
                float getP() { 1 }
            }
            class B {
                short getP() { 3 }
            }

            int foo(o) {
                if (o instanceof A || o instanceof B) {
                    return o.p.compareTo(2)
                }
            }
            assert foo(new A()) < 0
            assert foo(new B()) > 0
        '''
    }

    @Test
    void testMultipleInstanceOf2() {
        assertScript '''
            int cardinality(o) {
                (o instanceof List || o instanceof Map ? o.size() : 1)
            }
            assert cardinality('') == 1
            assert cardinality(['foo','bar']) == 2
            assert cardinality([foo:'',bar:'']) == 2
        '''
    }

    @Test
    void testMultipleInstanceOf3() {
        assertScript '''
            boolean empty(o) {
                (o instanceof List || o instanceof Map) && o.isEmpty()
            }
            assert !empty('')
            assert  empty([])
            assert  empty([:])
            assert !empty(['foo'])
            assert !empty([foo:null])
        '''
    }

    // GROOVY-8965
    @Test
    void testMultipleInstanceOf4() {
        assertScript '''
            def foo(o) {
                if (o instanceof Integer || o instanceof Double) {
                    o.floatValue() // ClassCastException
                }
            }
            def bar = foo(1.1d)
            assert bar == 1.1f
            def baz = foo(1)
            assert baz == 1
        '''
    }

    // GROOVY-11559
    @Test
    void testMultipleInstanceOf5() {
        assertScript '''
            def foo(o) {
                if (o instanceof Set || o instanceof List) {
                    o = "" // NullPointerException
                }
            }
            foo("")
            foo([])
        '''
    }

    @Test
    void testMultipleInstanceOf6() {
        assertScript '''
            void test(thing) {
                if (thing instanceof Deque) {
                    thing.addFirst(1) // 'addFirst' only in Deque
                } else if (thing instanceof Stack) {
                    thing.addElement(2) // 'addElement' only in Stack
                }
                if (thing instanceof Deque || thing instanceof Stack) {
                    assert thing.peek() in 1..2 // 'peek' in Deque and Stack but not LUB
                }
            }
            test(new Stack())
            test(new ArrayDeque())
        '''
    }

    @Test
    void testMultipleInstanceOf7() {
        assertScript '''
            class C extends AbstractCollection {
                Iterator<?> iterator() { }
                void clear() { }
                int peek() { 3 }
                int size() { 1 }
            }
            void test(which) {
                def thing = switch (which) {
                    case 01 -> new ArrayDeque()
                    case 02 -> new Stack()
                    default -> new C()
                }
                thing.clear()
                if (thing instanceof Deque) {
                    thing.addFirst(1)
                } else if (thing instanceof Stack) {
                    thing.addElement(2)
                }
                if (thing instanceof C || thing instanceof Queue || thing instanceof Stack) {
                    assert thing.peek() in 1..3
                }
            }
            test(1)
            test(2)
            test(3)
        '''
    }

    // GROOVY-10668
    @Test
    void testMultipleInstanceOf8() {
        for (string in ['(value as String)', 'value.toString()']) {
            assertScript """
                def toArray(Object value) {
                    def array
                    if (value instanceof List) {
                        array = value.toArray()
                    } else if (value instanceof String || value instanceof GString) {
                        array = ${string}.split(',')
                    } else {
                        throw new Exception('not supported')
                    }
                    return array
                }
                assert toArray([1,2,3]) == new Object[]{1,2,3}
                assert toArray('1,2,3') == new String[]{'1','2','3'}
                assert toArray("\${1}") == new String[]{'1'}
            """
        }
    }

    // GROOVY-10702
    @Test
    void testMultipleInstanceOf9() {
        shouldFailWithMessages '''
            def toArray(value) {
                def array
                if (value instanceof String || value instanceof GString) {
                    array = value.split(',') // split(String) not declared for GString/CharSequence
                }
                array
            }
        ''',
        'Cannot find matching method (java.lang.String | groovy.lang.GString)#split(java.lang.String)'

        shouldFailWithMessages '''
            def test(o) {
                (o instanceof List || o instanceof Map) ? o.entrySet() : null
            }
            test([])
        ''',
        'Cannot find matching method (java.util.List | java.util.Map)#entrySet()'
    }

    // GROOVY-6429
    @Test
    void testNotInstanceof1() {
        String types = '''
            class C {
            }
            class D extends C {
                def foo() { }
            }
        '''
        for (test in ['!(x instanceof D)', 'x !instanceof D']) {
            assertScript types + """
                void p(x) {
                    if ($test) {
                        // ...
                    } else {
                        x.foo()
                    }
                }
                p(new C())
                p(new D())
            """
            assertScript types + """
                void p(x) {
                    if ($test) {
                        return
                    }
                    assert x instanceof D
                    @ASTTest(phase=INSTRUCTION_SELECTION, value={
                        def ecks = node.rightExpression.objectExpression
                        def type = ecks.getNodeMetaData(INFERRED_TYPE)
                        assert type.text == 'D' // not <UnionType:D+D>
                    })
                    def bar = x.foo()
                }
                p(new C())
                p(new D())
            """
        }
    }

    // GROOVY-8523
    @Test
    void testNotInstanceof2() {
        for (test in ['!(x instanceof Runnable)', 'x !instanceof Runnable']) {
            assertScript """
                class Test {
                    public static int result = 0

                    static void p(x) {
                        if ($test) {
                            result = 1
                            return
                        }
                        q(x)
                    }

                    private static void q(Runnable r) {
                        result = 2
                    }
                }
                Test.p({->} as Runnable)
                assert Test.result == 2
                Test.p([''])
                assert Test.result == 1
            """
        }
    }

    // GROOVY-8523
    @Test
    void testNotInstanceOf3() {
        for (test in ['!(x instanceof List)', 'x !instanceof List']) {
            assertScript """
                class Test {
                    public static int result = 0

                    static void p(x) {
                        if (x !instanceof Runnable) {
                            result = 1
                            return
                        }
                        if ($test) {
                            result = 2
                            return
                        }
                        q(x)
                    }

                    private static void q(Runnable r) { // and List
                        result = 3
                    }
                }
                // TODO: List and Runnable
                Test.p({->} as Runnable)
                assert Test.result == 2
                Test.p([''])
                assert Test.result == 1
            """
        }
    }

    // GROOVY-9455
    @Test
    void testNotInstanceOf4() {
        for (test in ['!(x instanceof String)', 'x !instanceof String']) {
            shouldFailWithMessages """
                void p(x) {
                    if ($test) {
                        x.toUpperCase()
                    }
                }
            """,
            'Cannot find matching method java.lang.Object#toUpperCase()'
        }
    }

    // GROOVY-9931
    @Test
    void testNotInstanceof5() {
        for (test in ['!(x instanceof Number)', 'x !instanceof Number']) {
            assertScript """
                Number f(x) {
                    if ($test) {
                        return null
                    } else {
                        return x
                    }
                }
                assert f(42) == 42
                assert f('') == null
            """
        }
    }

    // GROOVY-8412
    @Test
    void testNotInstanceof6() {
        for (test in ['!(x instanceof Number)', 'x !instanceof Number']) {
            assertScript """
                Number f(x) {
                    return ($test) ? null : x
                }
                assert f(42) == 42
                assert f('') == null
            """
            assertScript """
                Number f(x) {
                    return !!($test) ? null : x // multiple negation
                }
                assert f(42) == 42
                assert f('') == null
            """
        }
    }

    // GROOVY-11983: !instanceof inside && must not produce a positive smart-cast in the else branch
    @Test
    void testNotInstanceof7() {
        for (test in ['!(x instanceof String)', 'x !instanceof String']) {
            // if/else form: a non-String x with cond=false lands in the else; if x were
            // smart-cast to String, the body would emit a checkcast and throw CCE
            assertScript """
                @groovy.transform.CompileStatic
                class T {
                    static int f(Object x, boolean cond) {
                        if (cond && $test) {
                            return 1
                        } else {
                            return x.hashCode()
                        }
                    }
                }
                assert T.f(42, false) == Integer.valueOf(42).hashCode()
                assert T.f('s', false) == 's'.hashCode()
                assert T.f('s', true)  == 's'.hashCode()
                assert T.f(42, true)   == 1
            """
            // ternary form
            assertScript """
                @groovy.transform.CompileStatic
                class T {
                    static int g(Object x, boolean cond) {
                        return (cond && $test) ? 1 : x.hashCode()
                    }
                }
                assert T.g(42, false) == Integer.valueOf(42).hashCode()
                assert T.g('s', false) == 's'.hashCode()
            """
            // early-return surrounding-scope form
            assertScript """
                @groovy.transform.CompileStatic
                class T {
                    static int h(Object x, boolean cond) {
                        if (cond && $test) return 1
                        return x.hashCode()
                    }
                }
                assert T.h(42, false) == Integer.valueOf(42).hashCode()
                assert T.h('s', false) == 's'.hashCode()
            """
        }
    }

    // GROOVY-11983: bitwise & / ^ between booleans, and bitwise | wrapping a logical-and,
    // must also disable the else-branch instanceof inversion
    @Test
    void testNotInstanceof8() {
        // bitwise-AND: same unsoundness as logical-AND
        assertScript '''
            @groovy.transform.CompileStatic
            class T {
                static int f(Object x, boolean cond) {
                    if (cond & !(x instanceof String)) return 1
                    return x.hashCode()
                }
            }
            assert T.f(42, false) == Integer.valueOf(42).hashCode()
            assert T.f('s', false) == 's'.hashCode()
            assert T.f('s', true) == 's'.hashCode()
            assert T.f(42, true) == 1
        '''
        // bitwise-XOR: same unsoundness
        assertScript '''
            @groovy.transform.CompileStatic
            class T {
                static int g(Object x, boolean cond) {
                    if (cond ^ (x instanceof String)) return 1
                    return x.hashCode()
                }
            }
            assert T.g(42, false) == Integer.valueOf(42).hashCode()
            assert T.g('s', true) == 's'.hashCode()
        '''
        // bitwise-OR wrapping a logical-AND: still must disqualify
        assertScript '''
            @groovy.transform.CompileStatic
            class T {
                static int h(Object x, boolean a, boolean b) {
                    if (a | (b && !(x instanceof String))) return 1
                    return x.hashCode()
                }
            }
            assert T.h(42, false, false) == Integer.valueOf(42).hashCode()
            assert T.h('s', false, false) == 's'.hashCode()
            assert T.h(42, true, false) == 1
        '''
    }

    // GROOVY-11983: a leading NotExpression is always invertible — even when its operand
    // contains an AND-like operator. The else of `if (!E)` fires when E is true, and
    // visitNotExpression has already sign-flipped the tti once, so the else-branch
    // inversion flips it back, yielding entries that are sound for E being true.
    @Test
    void testNotInstanceof9() {
        // !(cond && (x instanceof T)): in the else, cond && (x instanceof T) holds —
        // so x IS T. The else branch should permit a method call exclusive to T.
        assertScript '''
            @groovy.transform.CompileStatic
            class T {
                static int f(Object x, boolean cond) {
                    if (!(cond && (x instanceof String))) {
                        return -1
                    } else {
                        return x.length() // x must be smart-cast to String
                    }
                }
            }
            assert T.f('hello', true) == 5
            assert T.f('hello', false) == -1
            assert T.f(42, true) == -1
        '''
        // !(cond && !(x instanceof T)): in the else, cond is true AND x is NOT T.
        // No checkcast should be emitted on x in the else branch.
        assertScript '''
            @groovy.transform.CompileStatic
            class T {
                static int g(Object x, boolean cond) {
                    if (!(cond && !(x instanceof String))) {
                        return -1
                    } else {
                        return x.hashCode() // x is some non-String Object — no smart-cast
                    }
                }
            }
            assert T.g('s', true) == -1
            assert T.g(42, true) == Integer.valueOf(42).hashCode()
            assert T.g(42, false) == -1
        '''
    }

    // GROOVY-10217
    @Test
    void testInstanceOfThenSubscriptOperator() {
        assertScript '''
            void test(Object o) {
                if (o instanceof List) {
                    assert o[0] == 1
                    def x = (List) o
                    assert x[0] == 1
                }
            }
            test([1])
        '''
    }

    @Test
    void testInstanceOfInferenceWithImplicitIt() {
        assertScript '''
            ['a', 'b', 'c'].each {
                if (it instanceof String) {
                    println it.toUpperCase()
                }
            }
        '''
    }

    @Test
    void testInstanceOfTypeInferenceWithDef() {
        assertScript '''
            def profile = ['Guillaume', 34, true]
            def item = profile[0]
            if (item instanceof String) {
                println item.toUpperCase()
            }
        '''
    }

    @Test
    void testInstanceOfTypeInferenceWithoutDef() {
        assertScript '''
            def profile = ['Guillaume', 34, true]
            if (profile[0] instanceof String) {
                println profile[0].toUpperCase()
            }
        '''
    }

    @Test
    void testInstanceOfInferenceWithProperty1() {
        assertScript '''
            class A {
                int x
            }
            def a
            if (a instanceof A) {
                a.x = 2
            }
        '''
    }

    @Test
    void testInstanceOfInferenceWithProperty2() {
        shouldFailWithMessages '''
            class A {
                int x
            }
            def a = new A()
            if (a instanceof A) {
                a.x = '2'
            }
        ''',
        'Cannot assign value of type java.lang.String to variable of type int'
    }

    @Test
    void testInstanceOfInferenceWithMissingProperty() {
        shouldFailWithMessages '''
            class A {
                int x
            }
            def a
            if (a instanceof A) {
                a.y = 2
            }
        ''',
        'No such property: y for class: A'
    }

    // GROOVY-9967
    @Test
    void testInstanceOfInferenceWithSubclassProperty() {
        assertScript '''
            class A {
                int i
            }
            class B extends A {
                String s = 'foo'
            }

            String scenario1(x) {
                (x instanceof String) ? x.toLowerCase() : 'bar'
            }
            String scenario2(B x) {
                x.s
            }
            String scenario2a(B x) {
                x.getS()
            }
            String scenario3(B x) {
                (x instanceof B) ? x.s : 'bar'
            }
            String scenario3a(B x) {
                (x instanceof B) ? x.getS() : 'bar'
            }
            String scenario4(A x) {
                (x instanceof B) ? x.s : 'bar' // Access to A#s is forbidden
            }
            String scenario4a(A x) {
                (x instanceof B) ? x.getS() : 'bar'
            }

            assert scenario1(null) == 'bar'
            assert scenario1('Foo') == 'foo'
            assert scenario2(new B()) == 'foo'
            assert scenario2a(new B()) == 'foo'
            assert scenario3(new B()) == 'foo'
            assert scenario3a(new B()) == 'foo'

            assert scenario4(new A()) == 'bar'
            assert scenario4(new B()) == 'foo'
            assert scenario4a(new A()) == 'bar'
            assert scenario4a(new B()) == 'foo'
        '''
    }

    @Test
    void testShouldNotAllowDynamicVariable() {
        shouldFailWithMessages '''
            String name = 'Guillaume'
            println naamme
        ''',
        'The variable [naamme] is undeclared'
    }

    @Test
    void testShouldNotFailWithWith() {
        assertScript '''
            class A {
                int x
            }
            def a = new A()
            a.with {
                x = 2 // should be recognized as a.x at compile time
            }
            assert a.x == 2
        '''
    }

    @Test
    void testShouldFailWithWith() {
        shouldFailWithMessages '''
            class A {
                int x
            }
            def a = new A()
            a.with {
                x = '2' // should be recognized as a.x at compile time and fail because of wrong type
            }
        ''',
        'Cannot assign value of type java.lang.String to variable of type int'
    }

    @Test
    void testShouldNotFailWithWithTwoClasses() {
        // we must make sure that type inference engine in this case
        // takes the same property as at runtime
        assertScript '''
            class A {
                int x
            }
            class B {
                String x
            }
            def a = new A()
            def b = new B()
            a.with {
                b.with {
                    x = '2' // should be recognized as b.x at compile time
                }
            }
            assert a.x == 0
            assert b.x == '2'
        '''
    }

    @Test
    void testShouldNotFailWithWithAndImplicitIt() {
        assertScript '''
            class A {
                int x
            }
            def a = new A()
            a.with {
                it.x = 2 // should be recognized as a.x at compile time
            }
            assert a.x == 2
        '''
    }

    @Test
    void testShouldNotFailWithWithAndExplicitIt() {
        assertScript '''
            class A {
                int x
            }
            def a = new A()
            a.with { it ->
                it.x = 2 // should be recognized as a.x at compile time
            }
            assert a.x == 2
        '''
    }

    @Test
    void testShouldFailWithWithAndWrongExplicitIt() {
        shouldFailWithMessages '''
            class A {
                int x
            }
            def a = new A()
            a.with { String it ->
                it.x = 2 // should be recognized as a.x at compile time
            }
        ''',
        'Expected type A for closure parameter: it'
    }

    @Test
    void testShouldNotFailWithInheritanceAndWith() {
         assertScript '''
             class A {
                 int x
                 void method() { println x }
             }
             class B extends A {
             }
             def b = new B()
             b.with {
                 x = 2 // should be recognized as b.x at compile time
             }
             assert b.x == 2
         '''
    }

    @Test
    void testCallMethodInWithContext() {
        assertScript '''
            class A {
                int method() { return 1 }
            }
            def a = new A()
            a.with {
                method()
            }
        '''
    }

    @Test
    void testCallMethodInWithContextAndShadowing() {
       // make sure that the method which is found in 'with' is actually the one from class A
       // which returns a String
       assertScript '''
            class A {
                String method() { return 'Cedric' }
            }

            int method() { 1 }

            def a = new A()
            a.with {
                method().toUpperCase()
            }
        '''

       // check that if we switch signatures, it fails
       shouldFailWithMessages '''
            class A {
                int method() { 1 }
            }

            String method() { 'Cedric' }

            def a = new A()
            a.with {
                method().toUpperCase()
            }
        ''',
        'Cannot find matching method java.lang.Integer#toUpperCase()'
    }

    @Test
    void testDeclarationTypeInference() {
        Reference<MethodNode> method = []
        config.addCompilationCustomizers(new CompilationCustomizer(CompilePhase.CLASS_GENERATION) {
            @Override
            void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
                method.set(classNode.methods.find { it.name == 'method' })
            }
        })

        assertScript '''
            void method() {
                def o
                o = 1
                o = 'String'
            }
        '''
        ClassNode type = method.code.statements[0].expression.leftExpression.getNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE)
        assert type instanceof WideningCategories.LowestUpperBoundClassNode
        assert ClassHelper.make(Comparable  ) in type.interfaces
        assert ClassHelper.make(Serializable) in type.interfaces

        assertScript '''
            void method() {
                def o
                o = 1
                o = 2
            }
        '''
        type = method.code.statements[0].expression.leftExpression.getNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE)
        assert type == ClassHelper.int_TYPE

        assertScript '''
            void method() {
                def o
                o = 1L
                o = 2
            }
        '''
        type = method.code.statements[0].expression.leftExpression.getNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE)
        assert type  == ClassHelper.long_TYPE

        assertScript '''
            void method() {
                def o
                o = new HashSet()
                o = new LinkedHashSet()
            }
        '''
        type = method.code.statements[0].expression.leftExpression.getNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE)
        assert type == ClassHelper.make(HashSet)
    }

    @Test
    void testChooseMethodWithTypeInference() {
        assertScript '''
            void method(Object o) { println 'Object' }
            void method(int i) { println 'int' }
            def obj = 1
            method(obj)
        '''
    }

    @Test
    void testStarOperatorOnMap() {
        assertScript '''
            List keys = [x:1,y:2,z:3]*.key
            List values = [x:1,y:2,z:3]*.value
        '''
    }

    @Test
    void testStarOperatorOnMap2() {
        assertScript '''
            List keys = [x:1,y:2,z:3]*.key
            List values = [x:'1',y:'2',z:'3']*.value
            keys*.toUpperCase()
            values*.toUpperCase()
        '''

        shouldFailWithMessages '''
            List values = [x:1,y:2,z:3]*.value
            values*.toUpperCase()
        ''',
        'Cannot find matching method java.lang.Integer#toUpperCase()'
    }

    @Test
    void testStarOperatorOnMap3() {
        assertScript '''
            def keys = [x:1,y:2,z:3]*.key
            def values = [x:'1',y:'2',z:'3']*.value
            keys*.toUpperCase()
            values*.toUpperCase()
        '''

        shouldFailWithMessages '''
            def values = [x:1,y:2,z:3]*.value
            values*.toUpperCase()
        ''',
        'Cannot find matching method java.lang.Integer#toUpperCase()'
    }

    @Test
    void testStarOperatorOnMap4() {
        assertScript '''
            def map = [x:1,y:2,z:3]
            map*.value = 0

            assert map*.value == [0,0,0]
        '''

        assertScript '''
            Map<String,? extends Object> map = [x:1,y:2,z:3]
            map*.value = 0

            assert map*.value == [0,0,0]
        '''

        // GROOVY-10325
        assertScript '''
            Map<String,Object> map = [x:1,y:2,z:3]
            map*.value = 0 // was: Cannot assign List<Integer> to List<Object>

            assert map*.value == [0,0,0]
        '''

        shouldFailWithMessages '''
            [x:1,y:2,z:3]*.value = ""
        ''',
        'Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.Integer>'

        // GROOVY-10326
        shouldFailWithMessages '''
            [x:1,y:2,z:3]*.key = null
        ''',
        'Cannot set read-only property: key'
    }

    // GROOVY-7247
    @Test
    void testStarOperatorOnMapKey() {
        assertScript '''
            Map<String, Integer> map = [*:[A:1], *:[B:2]]
            assert map == [A:1, B:2]
        '''

        assertScript '''
            Map<String, Integer> one = [A:1]
            Map<String, Integer> two = [B:2]
            def map = [*:one, *:two]
            assert map == [A:1, B:2]
        '''

        assertScript '''
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                def type = node.getNodeMetaData(INFERRED_TYPE)
                assert type.genericsTypes[0].type == STRING_TYPE
                assert type.genericsTypes[1].type != Integer_TYPE
                assert type.genericsTypes[1].type.isDerivedFrom(Number_TYPE)
            })
            def map = [*:[A:1], *:[B:2.3]]
        '''

        shouldFailWithMessages '''
            Map<String, Integer> map = [*:[A:1], *:[B:2.3]]
        ''',
        'Cannot assign java.util.LinkedHashMap<java.lang.String, java.lang.Number',' to: java.util.Map<java.lang.String, java.lang.Integer>'
    }

    @Test
    void testFlowTypingWithStringVariable() {
        assertScript '''
            String s = new Object() // anything assignable to String
            s.toUpperCase()
        '''
    }

    @Test
    void testDefTypeAfterLongThenIntAssignments() {
        assertScript '''
            def o
            o = 1L
            o = 2
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                assert node.rightExpression.accessedVariable.getNodeMetaData(DECLARATION_INFERRED_TYPE) == long_TYPE
            })
            def z = o
        '''
    }

    // GROOVY-5519
    @Test
    void testInferThrowable() {
        assertScript '''
            try {
                throw new RuntimeException('ok')
            } catch (e) {
                handleError(e)
            }
            void handleError(Throwable e) {
                assert e.message == 'ok'
            }
        '''
    }

    // GROOVY-5522
    @Test
    void testTypeInferenceWithArrayAndFind() {
        assertScript '''
            File findFile() {
                new File[0].find { File f -> f.hidden }
            }
            findFile()
        '''
    }

    @Test
    void testShouldNotThrowIncompatibleArgToFunVerifyError() {
        assertScript '''
            Object convertValueToType(Object value, Class targetType) {
                if (value instanceof CharSequence) {
                    value = value.toString()
                }
                if (value instanceof String) {
                    String strValue = value.trim()
                }
            }
            convertValueToType('foo', String)
        '''
    }

    @Test
    void testSwitchCaseAnalysis1() {
        assertScript '''
            import org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode as LUB

            def method(int x) {
                def returnValue = new Date()
                switch (x) {
                  case 1:
                    returnValue = 'string'
                    break
                  case 2:
                    returnValue = 42
                    break
                }
                @ASTTest(phase=INSTRUCTION_SELECTION, value={
                    def type = node.getNodeMetaData(INFERRED_TYPE)
                    assert type instanceof LUB
                    assert type.name == 'java.io.Serializable'
                })
                def val = returnValue

                returnValue
            }
        '''
    }

    // GROOVY-6215
    @Test
    void testSwitchCaseAnalysis2() {
        assertScript '''
            def getValueForNumber(int x) {
                def valueToReturn
                switch(x) {
                  case 1:
                    valueToReturn = 'One'
                    break
                  case 2:
                    valueToReturn = []
                    valueToReturn << 'Two'
                    break
                }
                valueToReturn
            }

            def v1 = getValueForNumber(1)
            def v2 = getValueForNumber(2)
            def v3 = getValueForNumber(3)
            assert v1 == 'One'
            assert v2 == ['Two']
            assert v3 == null
        '''
    }

    // GROOVY-8411
    @Test
    void testSwitchCaseAnalysis3() {
        shouldFailWithMessages '''
            void test(something) {
                switch (something) {
                  case Class:
                    break
                  case File:
                    something.canonicalName
                }
            }
        ''',
        'No such property: canonicalName for class: java.io.File'

        shouldFailWithMessages '''
            void test(something) {
                switch (something) {
                  case Class:
                    break
                  default:
                    something.canonicalName
                }
            }
        ''',
        'No such property: canonicalName for class: java.lang.Object'

        shouldFailWithMessages '''
            void test(something) {
                switch (something) {
                  case Class:
                  case File:
                    something.toString()
                  default:
                    something.canonicalName // GROOVY-10702
                }
            }
        ''',
        'No such property: canonicalName for class: (java.lang.Class | java.io.File | java.lang.Object)'

        shouldFailWithMessages '''
            void test(something) {
                switch (something) {
                  case Class:
                  case File:
                    something.toString()
                }
                something.canonicalName
            }
        ''',
        'No such property: canonicalName for class: java.lang.Object'

        assertScript '''
            void test(something) {
                switch (something) {
                  case Class.class:
                    something.canonicalName
                    break
                  case File:
                    something.canonicalPath
                    break
                  default:
                    something?.toString()
                }
            }
        '''

        assertScript '''
            void test(something) {
                switch (something) {
                  case Float:
                  case Double:
                  case Integer:
                    something.doubleValue()
                }
            }
            test(1)
            test(1.1d)
            test(1.1f)
            test('11')
        '''
    }

    @Test
    void testNumberPrefixPlusPlusInference() {
        [Byte:'Integer',
         Character: 'Character',
         Short: 'Integer',
         Integer: 'Integer',
         Long: 'Long',
         Float: 'Double',
         Double: 'Double',
         BigDecimal: 'BigDecimal',
         BigInteger: 'BigInteger'
        ].each { orig, dest ->
            assertScript """
                $orig b = 65 as $orig
                @ASTTest(phase=INSTRUCTION_SELECTION, value={
                    def type = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
                    assert type == make($dest)
                })
                def pp = ++b
                println '++${orig} -> ' + pp.class + ' ' + pp
                assert pp.class == ${dest}
            """
        }
    }

    @Test
    void testNumberPostfixPlusPlusInference() {
        [Byte:'Byte',
         Character: 'Character',
         Short: 'Short',
         Integer: 'Integer',
         Long: 'Long',
         Float: 'Float',
         Double: 'Double',
         BigDecimal: 'BigDecimal',
         BigInteger: 'BigInteger'
        ].each { orig, dest ->
            assertScript """
                $orig b = 65 as $orig
                @ASTTest(phase=INSTRUCTION_SELECTION, value={
                    def type = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
                    assert type == make($dest)
                })
                def pp = b++
                println '${orig}++ -> ' + pp.class + ' ' + pp
                assert pp.class == ${dest}
            """
        }
    }

    // GROOVY-6522
    @Test
    void testInferenceWithImplicitClosureCoercion() {
        assertScript '''
            interface CustomCallable<T> {
                T call()
            }

            class Thing {
                static <T> T customType(CustomCallable<T> callable) {
                    callable.call()
                }

                @ASTTest(phase=INSTRUCTION_SELECTION, value={
                    lookup('test').each {
                        def call = it.expression
                        def type = call.getNodeMetaData(INFERRED_TYPE)
                        assert type.implementsInterface(LIST_TYPE)
                    }
                })
                static void run() {
                    test: customType { [] } // return type is not inferred - fails compile
                }
            }

            Thing.run()
        '''
    }

    @Test
    void testInferenceWithImplicitClosureCoercionAndArrayReturn() {
        assertScript '''
            interface ArrayFactory<T> { T[] array() }

            public <T> T[] intArray(ArrayFactory<T> f) {
                f.array()
            }
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                assert node.getNodeMetaData(INFERRED_TYPE) == Integer_TYPE.makeArray()
            })
            def array = intArray { new Integer[8] }
            assert array.length == 8
        '''
    }

    @Test
    void testInferenceWithImplicitClosureCoercionAndListReturn() {
        assertScript '''
            interface ListFactory<T> { List<T> list() }

            public <T> List<T> list(ListFactory<T> f) {
                f.list()
            }

            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                def type = node.getNodeMetaData(INFERRED_TYPE)
                assert type == LIST_TYPE
                assert type.genericsTypes[0].type == Integer_TYPE
            })
            def res = list { new LinkedList<Integer>() }
            assert res.size() == 0
        '''
    }

    // GROOVY-6835
    @Test
    void testFlowTypingWithInstanceofAndInterfaceTypes() {
        assertScript '''
            class ShowFlowTypeBug {
                Map<String,Object> instanceMap = (Map<String,Object>)[a: 'Hello World']
                def findInstance(String key) {
                    Set<CharSequence> instanceSet = []
                    Object instance = instanceMap.get(key)
                    if(instance instanceof CharSequence) {
                       instanceSet.add(instance)
                    }
                    instanceSet
                }
            }
            assert new ShowFlowTypeBug().findInstance('a') == ['Hello World'] as Set
        '''
    }

    @Test
    void testInferenceWithImplicitClosureCoercionAndGenericTypeAsParameter() {
        assertScript '''
            interface Action<T> { void execute(T t) }

            public <T> void exec(T t, Action<T> f) {
                f.execute(t)
            }

            exec('foo') { println it.toUpperCase() }
        '''
    }

    // GROOVY-6574
    @Test
    void testShouldInferPrimitiveBoolean() {
        assertScript '''
            def foo(Boolean o) {
                @ASTTest(phase=INSTRUCTION_SELECTION, value={
                    assert node.getNodeMetaData(INFERRED_TYPE) == boolean_TYPE
                })
                boolean b = o
                println b
            }
        '''
    }

    // GROOVY-7549
    @Test
    void testShouldKeepDeclTypeWhenAssignedInaccessibleT() {
        config.targetDirectory = File.createTempDir()
        def parentDir = File.createTempDir()
        try {
            new File(parentDir, 'a').mkdir()
            new File(parentDir, 'b').mkdir()

            def a = new File(parentDir, 'a/Main.groovy')
            a.write '''
                package a
                class Main {
                  static main(args) {
                    Face f = b.Maker.make() // returns b.Impl
                    assert f.meth() == 1234
                  }
                }
            '''
            def b = new File(parentDir, 'a/Face.groovy')
            b.write '''
                package a
                interface Face {
                  int meth()
                }
            '''
            def c = new File(parentDir, 'b/Impl.groovy')
            c.write '''
                package b
                @groovy.transform.PackageScope
                class Impl implements a.Face {
                  int meth() {
                    1234
                  }
                }
            '''
            def d = new File(parentDir, 'b/Maker.groovy')
            d.write '''
                package b
                class Maker {
                  static Impl make() { // probably should return a.Face
                    new Impl()
                  }
                }
            '''

            def loader = new GroovyClassLoader(this.class.classLoader)
            def cu = new CompilationUnit(config, null, loader)
            cu.addSources(a, b, c, d)
            cu.compile()

            loader.addClasspath(config.targetDirectory.absolutePath)
            loader.loadClass('a.Main', true).main()
        } finally {
            config.targetDirectory.deleteDir()
            parentDir.deleteDir()
        }
    }

    // GROOVY-5655
    @Test
    void testByteArrayInference() {
        assertScript '''
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                assert node.getNodeMetaData(INFERRED_TYPE) == byte_TYPE.makeArray()
            })
            def b = "foo".bytes
            new String(b)
        '''
    }

    // GROOVY-11623
    @Test
    void testAnnotationDefaults() {
        assertScript '''
            import java.lang.annotation.*

            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.TYPE)
            @interface A {
                int     iType() default 1
                long    lType() default 1
                short   sType() default 1
                float   fType() default 1f
                double  dType() default 1d
                boolean bType() default true
                char    cType() default 'c'
                String  chars() default 's'
                Class   tType() default Object
                Class[] xType() default [] // TODO: {} -- GROOVY-11492
            }

            @A class C {
            }

            assert C.getAnnotation(A).iType() == 1
            assert C.getAnnotation(A).lType() == 1L
            assert C.getAnnotation(A).sType() == (short) 1
            assert C.getAnnotation(A).fType() == 1.0f
            assert C.getAnnotation(A).dType() == 1.0d
            assert C.getAnnotation(A).bType() == true
            assert C.getAnnotation(A).cType() == (char) 'c'
            assert C.getAnnotation(A).chars() == "s"
            assert C.getAnnotation(A).tType() == Object.class
            assert C.getAnnotation(A).xType() == new Class[0]
        '''
    }

    // GROOVY-6207
    @Test
    void testGetAnnotationFails() {
        assertScript '''
            import groovy.transform.*
            import java.lang.annotation.*

            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.FIELD)
            @interface Tag1 {}

            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.FIELD)
            @interface Tag2 {}

            class A {
                @Tag2
                String field
            }

            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                lookup('second').each {
                    assert it.expression.getNodeMetaData(INFERRED_TYPE).name == 'Tag2'
                }
            })
            def doit(obj, String propName) {
                def field = obj.getClass().getDeclaredField(propName)
                if (field) {
                    @ASTTest(phase=INSTRUCTION_SELECTION, value={
                        assert node.getNodeMetaData(INFERRED_TYPE).name == 'Tag1'
                    })
                    def annotation = field.getAnnotation Tag1
                    if(true) {
                        second: annotation = field.getAnnotation Tag2
                    }
                    return annotation
                }
                return null
            }

            assert Tag2.isAssignableFrom(doit(new A(), 'field').class)
        '''
    }

    // GROOVY-9077
    @Test
    void testInferredTypeForPropertyThatResolvesToMethod() {
        assertScript '''
            import groovy.transform.*

            void test() {
                def items = [1, 2] as LinkedList

                @ASTTest(phase=INSTRUCTION_SELECTION, value={
                    node = node.rightExpression
                    assert node.class.name.contains('PropertyExpression')
                    def target = node.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)
                    assert target != null
                    assert target.declaringClass.name == 'java.util.LinkedList'
                })
                def one = items.first

                @ASTTest(phase=CLASS_GENERATION, value={
                    node = node.rightExpression
                    assert node.class.name.contains('MethodCallExpression')
                    def target = node.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)
                    assert target != null
                    assert target.declaringClass.name == 'java.util.LinkedList'
                })
                def alsoOne = items.peek()
            }

            test()
        '''
    }

    // GROOVY-10089
    @Test
    void testInferredTypeForMapOfList() {
        assertScript '''
            void test(... attributes) {
                List one = [
                    [id:'x', options:[count:1]]
                ]
                List two = attributes.collect {
                    def node = Collections.singletonMap('children', one)
                    if (node) {
                        node = node.get('children').find { child -> child['id'] == 'x' }
                    }
                    // inferred type of node must be something like Map<String,List<...>>

                    [id: it['id'], name: node['name'], count: node['options']['count']]
                    //                                        ^^^^^^^^^^^^^^^ GroovyCastException (map ctor for Collection)
                }
            }

            test( [id:'x'] )
        '''
    }

    // GROOVY-10328
    @Test
    void testInferredTypeForMapOrList() {
        assertScript '''
            @ASTTest(phase=INSTRUCTION_SELECTION, value={
                for (decl in node.code.statements*.expression) {
                    assert decl.getNodeMetaData(INFERRED_TYPE) == OBJECT_TYPE
                }
            })
            void test(List<? super String> list, Map<String, ? super String> map) {
                def a = list.first()
                def b = list.get(0)
                def c = list[0]

                def x = map.get('foo')
                def y = map['foo']
                def z = map.foo
            }
        '''
    }
}
