Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/t_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# Core infrastructure (must be loaded first)
require_relative "t_ruby/string_utils"
require_relative "t_ruby/ir"
require_relative "t_ruby/errors/type_slot_error"
require_relative "t_ruby/parser_combinator"
require_relative "t_ruby/scanner"
require_relative "t_ruby/smt_solver"
Expand All @@ -32,6 +33,9 @@
require_relative "t_ruby/runner"
require_relative "t_ruby/cli"

# Type Resolution
require_relative "t_ruby/type_resolution/slot_resolver"

# Milestone 4: Advanced Features
require_relative "t_ruby/constraint_checker"
require_relative "t_ruby/type_inferencer"
Expand Down
107 changes: 107 additions & 0 deletions lib/t_ruby/errors/type_slot_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# frozen_string_literal: true

module TRuby
module Errors
# TypeSlotError - Error with context-aware messaging based on TypeSlot
#
# Provides rich error messages with location info, context, and suggestions.
# Supports LSP diagnostic format for IDE integration.
class TypeSlotError < StandardError
attr_reader :type_slot, :original_message
attr_accessor :suggestion

def initialize(message:, type_slot: nil)
@type_slot = type_slot
@original_message = message
@suggestion = nil
super(message)
end

# Line number from type_slot location (1-indexed)
def line
type_slot&.location&.[](:line)
end

# Column number from type_slot location
def column
type_slot&.location&.[](:column)
end

# Kind of type slot (parameter, return, variable, etc.)
def kind
type_slot&.kind
end

# Format error message with location and context
def formatted_message
parts = []

# Location header
if line && column
parts << "Line #{line}, Column #{column}:"
elsif line
parts << "Line #{line}:"
end

# Context description
parts << context_description if type_slot

# Main error message (use original_message to avoid recursion)
parts << @original_message

# Suggestion if provided
parts << " Suggestion: #{suggestion}" if suggestion

parts.join("\n")
end

# Convert to LSP diagnostic format
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic
def to_lsp_diagnostic
start_line = line ? line - 1 : 0 # LSP uses 0-indexed lines
start_char = column || 0

{
range: {
start: { line: start_line, character: start_char },
end: { line: start_line, character: start_char + 1 },
},
message: message,
severity: 1, # 1 = Error
source: "t-ruby",
}
end

def to_s
formatted_message
end

private

def context_description
return nil unless type_slot

ctx = type_slot.context || {}

case kind
when :parameter
param_name = ctx[:param_name] || "unknown"
method_name = ctx[:method_name] || "unknown"
"in parameter '#{param_name}' of method '#{method_name}'"
when :return
method_name = ctx[:method_name] || "unknown"
"in return type of method '#{method_name}'"
when :variable
var_name = ctx[:var_name] || "unknown"
"in variable '#{var_name}'"
when :instance_var
var_name = ctx[:var_name] || "unknown"
"in instance variable '#{var_name}'"
when :generic_arg
type_name = ctx[:type_name] || "unknown"
"in generic argument of '#{type_name}'"
end
end
end
end
end
13 changes: 9 additions & 4 deletions lib/t_ruby/ir.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require_relative "ir/type_slot"

module TRuby
module IR
# Base class for all IR nodes
Expand Down Expand Up @@ -128,16 +130,17 @@ def children

# Method definition
class MethodDef < Node
attr_accessor :name, :params, :return_type, :body, :visibility, :type_params
attr_accessor :name, :params, :return_type, :body, :visibility, :type_params, :return_type_slot

def initialize(name:, params: [], return_type: nil, body: nil, visibility: :public, type_params: [], **opts)
def initialize(name:, params: [], return_type: nil, body: nil, visibility: :public, type_params: [], return_type_slot: nil, **opts)
super(**opts)
@name = name
@params = params
@return_type = return_type
@body = body
@visibility = visibility
@type_params = type_params
@return_type_slot = return_type_slot
end

def children
Expand All @@ -147,19 +150,21 @@ def children

# Method parameter
class Parameter < Node
attr_accessor :name, :type_annotation, :default_value, :kind, :interface_ref
attr_accessor :name, :type_annotation, :default_value, :kind, :interface_ref, :type_slot

# kind: :required, :optional, :rest, :keyrest, :block, :keyword
# :keyword - 키워드 인자 (구조분해): { name: String } → def foo(name:)
# :keyrest - 더블 스플랫: **opts: Type → def foo(**opts)
# interface_ref - interface 참조 타입 (예: }: UserParams 부분)
def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, **opts)
# type_slot - TypeSlot for this parameter's type annotation position
def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, type_slot: nil, **opts)
super(**opts)
@name = name
@type_annotation = type_annotation
@default_value = default_value
@kind = kind
@interface_ref = interface_ref
@type_slot = type_slot
end
end

Expand Down
57 changes: 57 additions & 0 deletions lib/t_ruby/ir/type_slot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module TRuby
module IR
# TypeSlot represents a position where a type annotation is expected.
# It tracks explicit, inferred, and resolved types for that position.
#
# @example Parameter type slot
# slot = TypeSlot.new(
# kind: :parameter,
# location: { line: 5, column: 10 },
# context: { method_name: "greet", param_name: "name" }
# )
# slot.explicit_type = SimpleType.new(name: "String")
#
class TypeSlot
KINDS = %i[parameter return variable instance_var generic_arg].freeze

attr_reader :kind, :location, :context
attr_accessor :explicit_type, :inferred_type, :resolved_type

# @param kind [Symbol] One of KINDS - the type of slot
# @param location [Hash] Location information (line, column)
# @param context [Hash] Additional context for error messages
def initialize(kind:, location:, context: {})
@kind = kind
@location = location
@context = context
@explicit_type = nil
@inferred_type = nil
@resolved_type = nil
end

# @return [Boolean] true if this slot needs type inference
def needs_inference?
@explicit_type.nil?
end

# @return [Hash] Context information for error messages
def error_context
{
kind: @kind,
location: @location,
context: @context,
}
end

# Returns the best available type, falling back to untyped
# Priority: resolved_type > explicit_type > inferred_type > untyped
#
# @return [TypeNode] The resolved type or untyped
def resolved_type_or_untyped
@resolved_type || @explicit_type || @inferred_type || SimpleType.new(name: "untyped")
end
end
end
end
Loading
Loading