lib/steep/diagnostic/signature.rb



module Steep
  module Diagnostic
    module Signature
      class Base
        include Helper

        attr_reader :location

        def initialize(location:)
          @location = location
        end

        def header_line
          raise
        end

        def detail_lines
          nil
        end

        def diagnostic_code
          "RBS::#{error_name}"
        end

        def path
          if location
            Pathname(location.buffer.name)
          end
        end
      end

      class SyntaxError < Base
        attr_reader :exception

        def initialize(exception, location:)
          super(location: location)
          @exception = exception
        end

        def self.parser_syntax_error_message(exception)
          string = exception.location.source.to_s
          unless string.empty?
            string = " (#{string})"
          end

          "Syntax error caused by token `#{exception.token_type}`#{string}"
        end

        def header_line
          exception.message
        end
      end

      class DuplicatedDeclaration < Base
        attr_reader :type_name

        def initialize(type_name:, location:)
          super(location: location)
          @type_name = type_name
        end

        def header_line
          "Declaration of `#{type_name}` is duplicated"
        end
      end

      class UnknownTypeName < Base
        attr_reader :name

        def initialize(name:, location:)
          super(location: location)
          @name = name
        end

        def header_line
          "Cannot find type `#{name}`"
        end
      end

      class InvalidTypeApplication < Base
        attr_reader :name
        attr_reader :args
        attr_reader :params

        def initialize(name:, args:, params:, location:)
          super(location: location)
          @name = name
          @args = args
          @params = params
        end

        def header_line
          case
          when params.empty?
            "Type `#{name}` is not generic but used as a generic type with #{args.size} arguments"
          when args.empty?
            "Type `#{name}` is generic but used as a non generic type"
          else
            "Type `#{name}` expects #{params.size} arguments, but #{args.size} arguments are given"
          end
        end
      end

      class UnsatisfiableTypeApplication < Base
        attr_reader :type_name
        attr_reader :type_arg
        attr_reader :type_param

        def initialize(type_name:, type_arg:, type_param:, location:)
          super(location: location)
          @type_name = type_name
          @type_arg = type_arg
          @type_param = type_param
        end

        def header_line
          "Type application of `#{type_name}` doesn't satisfy the constraints: #{type_arg} <: #{type_param.upper_bound}"
        end
      end

      class InvalidMethodOverload < Base
        attr_reader :class_name
        attr_reader :method_name

        def initialize(class_name:, method_name:, location:)
          super(location: location)
          @class_name = class_name
          @method_name = method_name
        end

        def header_line
          "Cannot find a non-overloading definition of `#{method_name}` in `#{class_name}`"
        end
      end

      class UnknownMethodAlias < Base
        attr_reader :class_name
        attr_reader :method_name

        def initialize(class_name:, method_name:, location:)
          super(location: location)
          @class_name = class_name
          @method_name = method_name
        end

        def header_line
          "Cannot find the original method `#{method_name}` in `#{class_name}`"
        end
      end

      class DuplicatedMethodDefinition < Base
        attr_reader :class_name
        attr_reader :method_name

        def initialize(class_name:, method_name:, location:)
          super(location: location)
          @class_name = class_name
          @method_name = method_name
        end

        def header_line
          "Non-overloading method definition of `#{method_name}` in `#{class_name}` cannot be duplicated"
        end
      end

      class RecursiveAlias < Base
        attr_reader :class_name
        attr_reader :names

        def initialize(class_name:, names:, location:)
          super(location: location)
          @class_name = class_name
          @names = names
        end

        def header_line
          "Circular method alias is detected in `#{class_name}`: #{names.join(" -> ")}"
        end
      end

      class RecursiveAncestor < Base
        attr_reader :ancestors

        def initialize(ancestors:, location:)
          super(location: location)
          @ancestors = ancestors
        end

        def header_line
          names = ancestors.map do |ancestor|
            case ancestor
            when RBS::Definition::Ancestor::Singleton
              "singleton(#{ancestor.name})"
            when RBS::Definition::Ancestor::Instance
              if ancestor.args.empty?
                ancestor.name.to_s
              else
                "#{ancestor.name}[#{ancestor.args.join(", ")}]"
              end
            end
          end

          "Circular inheritance/mix-in is detected: #{names.join(" <: ")}"
        end
      end

      class SuperclassMismatch < Base
        attr_reader :name

        def initialize(name:, location:)
          super(location: location)
          @name = name
        end

        def header_line
          "Different superclasses are specified for `#{name}`"
        end
      end

      class GenericParameterMismatch < Base
        attr_reader :name

        def initialize(name:, location:)
          super(location: location)
          @name = name
        end

        def header_line
          "Different generic parameters are specified across definitions of `#{name}`"
        end
      end

      class InvalidVarianceAnnotation < Base
        attr_reader :name
        attr_reader :param

        def initialize(name:, param:, location:)
          super(location: location)
          @name = name
          @param = param
        end

        def header_line
          "The variance of type parameter `#{param.name}` is #{param.variance}, but used in incompatible position here"
        end
      end

      class ModuleSelfTypeError < Base
        attr_reader :name
        attr_reader :ancestor
        attr_reader :relation

        def initialize(name:, ancestor:, relation:, location:)
          super(location: location)

          @name = name
          @ancestor = ancestor
          @relation = relation
        end

        def header_line
          "Module self type constraint in type `#{name}` doesn't satisfy: `#{relation}`"
        end
      end

      class ClassVariableDuplicationError < Base
        attr_reader :class_name
        attr_reader :other_class_name
        attr_reader :variable_name

        def initialize(class_name:, other_class_name:, variable_name:, location:)
          super(location: location)

          @class_name = class_name
          @other_class_name = other_class_name
          @variable_name = variable_name
        end

        def header_line
          "Class variable definition `#{variable_name}` in `#{class_name}` may be overtaken by `#{other_class_name}`"
        end
      end

      class InstanceVariableTypeError < Base
        attr_reader :name
        attr_reader :var_type
        attr_reader :parent_type

        def initialize(name:, location:, var_type:, parent_type:)
          super(location: location)

          @name = name
          @var_type = var_type
          @parent_type = parent_type
        end

        def header_line
          "Instance variable cannot have different type with parents: #{var_type} <=> #{parent_type}"
        end
      end

      class MixinClassError < Base
        attr_reader :member
        attr_reader :type_name

        def initialize(location:, member:, type_name:)
          super(location: location)
          @member = member
          @type_name = type_name
        end

        def header_line
          "Cannot #{mixin_name} a class `#{member.name}` in the definition of `#{type_name}`"
        end

        private

        def mixin_name
          case mem = member
          when RBS::AST::Members::Prepend
            "prepend"
          when RBS::AST::Members::Include
            "include"
          when RBS::AST::Members::Extend
            "extend"
          end
        end
      end

      class InheritModuleError < Base
        attr_reader :super_class

        def initialize(super_class)
          super(location: super_class.location)
          @super_class = super_class
        end

        def header_line
          "Cannot inherit from a module `#{super_class.name}`"
        end
      end

      class UnexpectedError < Base
        attr_reader :message

        def initialize(message:, location:)
          @message = message
          @location = location
        end

        def header_line
          "Unexpected error: #{message}"
        end
      end

      class RecursiveTypeAlias < Base
        attr_reader :alias_names

        def initialize(alias_names:, location:)
          @alias_names = alias_names
          super(location: location)
        end

        def header_line
          "Type aliases cannot be *directly recursive*: #{alias_names.join(", ")}"
        end
      end

      class NonregularTypeAlias < Base
        attr_reader :type_name
        attr_reader :nonregular_type

        def initialize(type_name:, nonregular_type:, location:)
          @type_name = type_name
          @nonregular_type = nonregular_type
          @location = location
        end

        def header_line
          "Type alias #{type_name} is defined *non-regular*: #{nonregular_type}"
        end
      end

      class InconsistentClassModuleAliasError < Base
        attr_reader :decl

        def initialize(decl:)
          @decl = decl
          super(location: decl.location&.[](:old_name))
        end

        def header_line
          expected_kind =
            case decl
            when RBS::AST::Declarations::ModuleAlias
              "module"
            when RBS::AST::Declarations::ClassAlias
              "class"
            else
              raise
            end

          "A #{expected_kind} `#{decl.new_name}` cannot be an alias of `#{decl.old_name}`"
        end
      end

      class CyclicClassAliasDefinitionError < Base
        attr_reader :decl

        def initialize(decl:)
          @decl = decl
          super(location: decl.location&.[](:new_name))
        end

        def header_line
          "#{decl.new_name} is a cyclic definition"
        end
      end

      def self.from_rbs_error(error, factory:)
        case error
        when RBS::ParsingError
          Diagnostic::Signature::SyntaxError.new(error, location: error.location)
        when RBS::DuplicatedDeclarationError
          Diagnostic::Signature::DuplicatedDeclaration.new(
            type_name: error.name,
            location: error.decls[0].location
          )
        when RBS::GenericParameterMismatchError
          Diagnostic::Signature::GenericParameterMismatch.new(
            name: error.name,
            location: error.decl.location
          )
        when RBS::InvalidTypeApplicationError
          Diagnostic::Signature::InvalidTypeApplication.new(
            name: error.type_name,
            args: error.args.map {|ty| factory.type(ty) },
            params: error.params,
            location: error.location
          )
        when RBS::NoTypeFoundError,
          RBS::NoSuperclassFoundError,
          RBS::NoMixinFoundError,
          RBS::NoSelfTypeFoundError
          Diagnostic::Signature::UnknownTypeName.new(
            name: error.type_name,
            location: error.location
          )
        when RBS::InvalidOverloadMethodError
          Diagnostic::Signature::InvalidMethodOverload.new(
            class_name: error.type_name,
            method_name: error.method_name,
            location: error.members[0].location
          )
        when RBS::DuplicatedMethodDefinitionError
          Diagnostic::Signature::DuplicatedMethodDefinition.new(
            class_name: error.type_name,
            method_name: error.method_name,
            location: error.location
          )
        when RBS::DuplicatedInterfaceMethodDefinitionError
          Diagnostic::Signature::DuplicatedMethodDefinition.new(
            class_name: error.type_name,
            method_name: error.method_name,
            location: error.member.location
          )
        when RBS::UnknownMethodAliasError
          Diagnostic::Signature::UnknownMethodAlias.new(
            class_name: error.type_name,
            method_name: error.original_name,
            location: error.location
          )
        when RBS::RecursiveAliasDefinitionError
          Diagnostic::Signature::RecursiveAlias.new(
            class_name: error.type.name,
            names: error.defs.map(&:name),
            location: error.defs[0].original&.location
          )
        when RBS::RecursiveAncestorError
          Diagnostic::Signature::RecursiveAncestor.new(
            ancestors: error.ancestors,
            location: error.location
          )
        when RBS::SuperclassMismatchError
          Diagnostic::Signature::SuperclassMismatch.new(
            name: error.name,
            location: error.entry.primary.decl.location
          )
        when RBS::InvalidVarianceAnnotationError
          Diagnostic::Signature::InvalidVarianceAnnotation.new(
            name: error.type_name,
            param: error.param,
            location: error.location
          )
        when RBS::MixinClassError
          Diagnostic::Signature::MixinClassError.new(
            location: error.location,
            type_name: error.type_name,
            member: error.member,
          )
        when RBS::RecursiveTypeAliasError
          Diagnostic::Signature::RecursiveTypeAlias.new(
            alias_names: error.alias_names,
            location: error.location
          )
        when RBS::NonregularTypeAliasError
          Diagnostic::Signature::NonregularTypeAlias.new(
            type_name: error.diagnostic.type_name,
            nonregular_type: factory.type(error.diagnostic.nonregular_type),
            location: error.location
          )
        when RBS::InheritModuleError
          Diagnostic::Signature::InheritModuleError.new(error.super_decl)
        when RBS::InconsistentClassModuleAliasError
          Diagnostic::Signature::InconsistentClassModuleAliasError.new(decl: error.alias_entry.decl)
        when RBS::CyclicClassAliasDefinitionError
          Diagnostic::Signature::CyclicClassAliasDefinitionError.new(decl: error.alias_entry.decl)
        else
          raise error
        end
      end
    end
  end
end