Skip to content

Commit 5059621

Browse files
authored
Parser architecture improvement with TypeSlot (#48)
* Add TypeSlot infrastructure for type position tracking Introduce TypeSlot class that represents positions where type annotations are expected. This enables explicit tracking of explicit, inferred, and resolved types for each position. - Add IR::TypeSlot class with kind, location, and context - Add type_slot attribute to IR::Parameter - Add return_type_slot attribute to IR::MethodDef - Maintain backward compatibility with existing code Refs #47 * Integrate TypeSlot generation in TokenDeclarationParser - Add TypeSlot creation during parameter parsing in parse_parameter - Add return_type_slot creation in parse_method_def - Pass method_name context to parameter parsers for TypeSlot context - Support all parameter types (regular, rest, keyrest, block, keyword) - Add comprehensive tests for TypeSlot integration - Maintain backward compatibility with existing type_annotation/return_type Refs #47 * Add TypeSlotError for context-aware error messages - Add TRuby::Errors::TypeSlotError class for type-related errors - Support location info (line, column) from TypeSlot - Include context description (parameter/return/variable info) - Support suggestion field for helpful error hints - Provide to_lsp_diagnostic for LSP integration - Add comprehensive tests Refs #47 * Add SlotResolver for TypeSlot resolution - Add TRuby::TypeResolution::SlotResolver class - Collect unresolved TypeSlots from IR program - Support resolve_to_untyped for gradual typing fallback - Provide slot_summary for type coverage statistics - Handle method parameters and return types - Traverse ClassDef and ModuleDef for nested methods Refs #47 * Add facade pattern for parser with token parser option - Add use_token_parser option to Parser.new for opt-in new parser - Support TRUBY_NEW_PARSER=1 environment variable - Implement parse_with_token_parser using TokenDeclarationParser - Add backward-compatible legacy format conversion - Mark legacy regex-based parser as @deprecated - Add comprehensive facade tests This enables gradual migration from regex-based parsing to the new TokenDeclarationParser with TypeSlot support. Refs #47
1 parent 873878d commit 5059621

File tree

13 files changed

+1277
-22
lines changed

13 files changed

+1277
-22
lines changed

lib/t_ruby.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# Core infrastructure (must be loaded first)
1010
require_relative "t_ruby/string_utils"
1111
require_relative "t_ruby/ir"
12+
require_relative "t_ruby/errors/type_slot_error"
1213
require_relative "t_ruby/parser_combinator"
1314
require_relative "t_ruby/scanner"
1415
require_relative "t_ruby/smt_solver"
@@ -32,6 +33,9 @@
3233
require_relative "t_ruby/runner"
3334
require_relative "t_ruby/cli"
3435

36+
# Type Resolution
37+
require_relative "t_ruby/type_resolution/slot_resolver"
38+
3539
# Milestone 4: Advanced Features
3640
require_relative "t_ruby/constraint_checker"
3741
require_relative "t_ruby/type_inferencer"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# frozen_string_literal: true
2+
3+
module TRuby
4+
module Errors
5+
# TypeSlotError - Error with context-aware messaging based on TypeSlot
6+
#
7+
# Provides rich error messages with location info, context, and suggestions.
8+
# Supports LSP diagnostic format for IDE integration.
9+
class TypeSlotError < StandardError
10+
attr_reader :type_slot, :original_message
11+
attr_accessor :suggestion
12+
13+
def initialize(message:, type_slot: nil)
14+
@type_slot = type_slot
15+
@original_message = message
16+
@suggestion = nil
17+
super(message)
18+
end
19+
20+
# Line number from type_slot location (1-indexed)
21+
def line
22+
type_slot&.location&.[](:line)
23+
end
24+
25+
# Column number from type_slot location
26+
def column
27+
type_slot&.location&.[](:column)
28+
end
29+
30+
# Kind of type slot (parameter, return, variable, etc.)
31+
def kind
32+
type_slot&.kind
33+
end
34+
35+
# Format error message with location and context
36+
def formatted_message
37+
parts = []
38+
39+
# Location header
40+
if line && column
41+
parts << "Line #{line}, Column #{column}:"
42+
elsif line
43+
parts << "Line #{line}:"
44+
end
45+
46+
# Context description
47+
parts << context_description if type_slot
48+
49+
# Main error message (use original_message to avoid recursion)
50+
parts << @original_message
51+
52+
# Suggestion if provided
53+
parts << " Suggestion: #{suggestion}" if suggestion
54+
55+
parts.join("\n")
56+
end
57+
58+
# Convert to LSP diagnostic format
59+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic
60+
def to_lsp_diagnostic
61+
start_line = line ? line - 1 : 0 # LSP uses 0-indexed lines
62+
start_char = column || 0
63+
64+
{
65+
range: {
66+
start: { line: start_line, character: start_char },
67+
end: { line: start_line, character: start_char + 1 },
68+
},
69+
message: message,
70+
severity: 1, # 1 = Error
71+
source: "t-ruby",
72+
}
73+
end
74+
75+
def to_s
76+
formatted_message
77+
end
78+
79+
private
80+
81+
def context_description
82+
return nil unless type_slot
83+
84+
ctx = type_slot.context || {}
85+
86+
case kind
87+
when :parameter
88+
param_name = ctx[:param_name] || "unknown"
89+
method_name = ctx[:method_name] || "unknown"
90+
"in parameter '#{param_name}' of method '#{method_name}'"
91+
when :return
92+
method_name = ctx[:method_name] || "unknown"
93+
"in return type of method '#{method_name}'"
94+
when :variable
95+
var_name = ctx[:var_name] || "unknown"
96+
"in variable '#{var_name}'"
97+
when :instance_var
98+
var_name = ctx[:var_name] || "unknown"
99+
"in instance variable '#{var_name}'"
100+
when :generic_arg
101+
type_name = ctx[:type_name] || "unknown"
102+
"in generic argument of '#{type_name}'"
103+
end
104+
end
105+
end
106+
end
107+
end

lib/t_ruby/ir.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require_relative "ir/type_slot"
4+
35
module TRuby
46
module IR
57
# Base class for all IR nodes
@@ -128,16 +130,17 @@ def children
128130

129131
# Method definition
130132
class MethodDef < Node
131-
attr_accessor :name, :params, :return_type, :body, :visibility, :type_params
133+
attr_accessor :name, :params, :return_type, :body, :visibility, :type_params, :return_type_slot
132134

133-
def initialize(name:, params: [], return_type: nil, body: nil, visibility: :public, type_params: [], **opts)
135+
def initialize(name:, params: [], return_type: nil, body: nil, visibility: :public, type_params: [], return_type_slot: nil, **opts)
134136
super(**opts)
135137
@name = name
136138
@params = params
137139
@return_type = return_type
138140
@body = body
139141
@visibility = visibility
140142
@type_params = type_params
143+
@return_type_slot = return_type_slot
141144
end
142145

143146
def children
@@ -147,19 +150,21 @@ def children
147150

148151
# Method parameter
149152
class Parameter < Node
150-
attr_accessor :name, :type_annotation, :default_value, :kind, :interface_ref
153+
attr_accessor :name, :type_annotation, :default_value, :kind, :interface_ref, :type_slot
151154

152155
# kind: :required, :optional, :rest, :keyrest, :block, :keyword
153156
# :keyword - 키워드 인자 (구조분해): { name: String } → def foo(name:)
154157
# :keyrest - 더블 스플랫: **opts: Type → def foo(**opts)
155158
# interface_ref - interface 참조 타입 (예: }: UserParams 부분)
156-
def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, **opts)
159+
# type_slot - TypeSlot for this parameter's type annotation position
160+
def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, type_slot: nil, **opts)
157161
super(**opts)
158162
@name = name
159163
@type_annotation = type_annotation
160164
@default_value = default_value
161165
@kind = kind
162166
@interface_ref = interface_ref
167+
@type_slot = type_slot
163168
end
164169
end
165170

lib/t_ruby/ir/type_slot.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
module TRuby
4+
module IR
5+
# TypeSlot represents a position where a type annotation is expected.
6+
# It tracks explicit, inferred, and resolved types for that position.
7+
#
8+
# @example Parameter type slot
9+
# slot = TypeSlot.new(
10+
# kind: :parameter,
11+
# location: { line: 5, column: 10 },
12+
# context: { method_name: "greet", param_name: "name" }
13+
# )
14+
# slot.explicit_type = SimpleType.new(name: "String")
15+
#
16+
class TypeSlot
17+
KINDS = %i[parameter return variable instance_var generic_arg].freeze
18+
19+
attr_reader :kind, :location, :context
20+
attr_accessor :explicit_type, :inferred_type, :resolved_type
21+
22+
# @param kind [Symbol] One of KINDS - the type of slot
23+
# @param location [Hash] Location information (line, column)
24+
# @param context [Hash] Additional context for error messages
25+
def initialize(kind:, location:, context: {})
26+
@kind = kind
27+
@location = location
28+
@context = context
29+
@explicit_type = nil
30+
@inferred_type = nil
31+
@resolved_type = nil
32+
end
33+
34+
# @return [Boolean] true if this slot needs type inference
35+
def needs_inference?
36+
@explicit_type.nil?
37+
end
38+
39+
# @return [Hash] Context information for error messages
40+
def error_context
41+
{
42+
kind: @kind,
43+
location: @location,
44+
context: @context,
45+
}
46+
end
47+
48+
# Returns the best available type, falling back to untyped
49+
# Priority: resolved_type > explicit_type > inferred_type > untyped
50+
#
51+
# @return [TypeNode] The resolved type or untyped
52+
def resolved_type_or_untyped
53+
@resolved_type || @explicit_type || @inferred_type || SimpleType.new(name: "untyped")
54+
end
55+
end
56+
end
57+
end

0 commit comments

Comments
 (0)