require "../../spec_helper"

describe "Visibility modifiers" do
  it "disallows invoking private method" do
    assert_error <<-CRYSTAL, "private method 'foo' called for Foo"
      class Foo
        private def foo
          1
        end
      end

      Foo.new.foo
      CRYSTAL
  end

  it "allows setting visibility modifier to macro" do
    assert_error <<-CRYSTAL, "private method 'foo' called for Foo"
      class Object
        macro x
          def foo
          end
        end
      end

      class Foo
        private x
      end

      Foo.new.foo
      CRYSTAL
  end

  it "allows setting visibility modifier to macro that generates many methods (1)" do
    assert_error <<-CRYSTAL, "private method 'foo' called for Foo"
      class Object
        macro x
          def foo
          end

          def bar
          end
        end
      end

      class Foo
        private x
      end

      Foo.new.foo
      CRYSTAL
  end

  it "allows setting visibility modifier to macro that generates many methods (2)" do
    assert_error <<-CRYSTAL, "private method 'bar' called for Foo"
      class Object
        macro x
          def foo
          end

          def bar
          end
        end
      end

      class Foo
        private x
      end

      Foo.new.bar
      CRYSTAL
  end

  it "allows invoking private method from the same class" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        private def foo
          1
        end

        def bar
          self.foo
        end
      end

      Foo.new.bar
      CRYSTAL
  end

  it "allows invoking protected method from the same class" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        protected def foo
          1
        end

        def bar
          self.foo
        end
      end

      Foo.new.bar
      CRYSTAL
  end

  it "allows invoking protected method from subclass" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        protected def foo
          1
        end
      end

      class Bar < Foo
        def bar
          Foo.new.foo
        end
      end

      Bar.new.bar
      CRYSTAL
  end

  it "allows invoking protected method from subclass (2)" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        protected def foo
          1
        end
      end

      class Bar < Foo
      end

      class Baz < Foo
        def baz
          Bar.new.foo
        end
      end

      Baz.new.baz
      CRYSTAL
  end

  it "errors if invoking protected method from top-level" do
    assert_error <<-CRYSTAL, "protected method 'foo' called for Foo"
      class Foo
        protected def foo
        end
      end

      Foo.new.foo
      CRYSTAL
  end

  it "errors if invoking protected method from non-subclass" do
    assert_error <<-CRYSTAL, "protected method 'foo' called for Foo"
      class Foo
        protected def foo
        end
      end

      class Bar
        def bar
          Foo.new.foo
        end
      end

      Bar.new.bar
      CRYSTAL
  end

  it "errors if invoking protected method from non-subclass, generated with macro that generates a macro" do
    assert_error <<-CRYSTAL, "protected method 'foo' called for Foo"
      class Object
        macro y
          def foo
          end
        end

        macro x
          y
        end
      end

      class Foo
        protected x
      end

      Foo.new.foo
      CRYSTAL
  end

  it "errors if applying visibility modifier to non-def or non-call" do
    assert_error <<-CRYSTAL, "can't apply visibility modifier"
      class Foo
        private 1
      end
      CRYSTAL
  end

  it "allows invoking protected from instance to class" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        def instance_foo
          Foo.class_foo
        end

        protected def self.class_foo
          1
        end
      end

      Foo.new.instance_foo
      CRYSTAL
  end

  it "automatically makes initialize be protected" do
    assert_error <<-CRYSTAL, "protected method 'initialize' called for Foo"
      class Foo
        def initialize(x)
        end
      end

      foo = Foo.new(1)
      foo.initialize(2)
      CRYSTAL
  end

  it "allows invoking private setter with self" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        private def x=(x)
          x
        end

        def foo
          self.x = 1
        end
      end

      Foo.new.foo
      CRYSTAL
  end

  it "allows invoking protected method from namespace to namespaced type" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        def foo
          Bar.new.bar
        end

        class Bar
          protected def bar
            1
          end
        end
      end

      Foo.new.foo
      CRYSTAL
  end

  it "allows invoking protected method from namespaced type to namespace" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        protected def foo
          1
        end

        class Bar
          def bar
            Foo.new.foo
          end
        end
      end

      Foo::Bar.new.bar
      CRYSTAL
  end

  it "allows invoking protected method between types in the same namespace" do
    assert_type(<<-CRYSTAL) { int32 }
      module NS1
        class NS2
          class Foo
            def foo
              Bar.new.bar
            end
          end

          class Bar
            protected def bar
              1
            end
          end
        end
      end

      NS1::NS2::Foo.new.foo
      CRYSTAL
  end

  it "allows invoking protected method between types in the same namespace when inheriting" do
    assert_type(<<-CRYSTAL) { int32 }
      module NS1
        class NS2
          class Foo
            def foo
              Bar.new.bar
            end
          end

          class Bar
            protected def bar
              1
            end
          end
        end
      end

      class MyFoo < NS1::NS2::Foo
      end

      MyFoo.new.foo
      CRYSTAL
  end

  it "allows invoking protected method from virtual type" do
    assert_type(<<-CRYSTAL) { union_of int32, float64 }
      abstract class Foo
        def foo
          bar
        end
      end

      class Bar < Foo
        protected def bar
          1
        end
      end

      class Baz < Foo
        protected def bar
          1.5
        end
      end

      (Bar.new || Baz.new).foo
      CRYSTAL
  end

  it "allows calling protected method from nested generic class (1)" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo
        class Bar(U)
          def bar
            Foo.new.foo
          end
        end

        protected def foo
          1
        end
      end

      Foo::Bar(Int32).new.bar
      CRYSTAL
  end

  it "allows calling protected method from nested generic class (2)" do
    assert_type(<<-CRYSTAL) { int32 }
      class Foo(T)
        class Bar(U)
          def bar
            Foo(Int32).new.foo
          end
        end

        protected def foo
          1
        end
      end

      Foo::Bar(Int32).new.bar
      CRYSTAL
  end

  it "gives correct error on unknown call (#2838)" do
    assert_error <<-CRYSTAL, "undefined local variable or method 'foo'"
      private foo
      CRYSTAL
  end

  it "defines protected initialize (#7501)" do
    assert_error <<-CRYSTAL, "protected method 'new' called for Foo.class"
      class Foo
        protected def initialize
        end
      end

      Foo.new
      CRYSTAL
  end

  it "handles virtual types (#8561)" do
    assert_no_errors <<-CRYSTAL
      module Namespace
        class Foo
          protected def foo
          end
        end

        class Bar
          def bar
            Foo.new.foo
          end
        end

        class Baz < Bar
          def initialize
            @bar = Bar.new
          end

          def bar
            @bar.bar
          end
        end
      end

      Namespace::Baz.new.bar
      CRYSTAL
  end
end
