Class: TRuby::TypeChecker
- Inherits:
-
Object
- Object
- TRuby::TypeChecker
- Defined in:
- lib/t_ruby/type_checker.rb
Overview
Main type checker with SMT solver integration
Direct Known Subclasses
Instance Attribute Summary collapse
-
#errors ⇒ Object
readonly
Returns the value of attribute errors.
-
#hierarchy ⇒ Object
readonly
Returns the value of attribute hierarchy.
-
#inferencer ⇒ Object
readonly
Returns the value of attribute inferencer.
-
#smt_solver ⇒ Object
readonly
Returns the value of attribute smt_solver.
-
#use_smt ⇒ Object
readonly
Returns the value of attribute use_smt.
-
#warnings ⇒ Object
readonly
Returns the value of attribute warnings.
Instance Method Summary collapse
-
#check_assignment(variable, value, declared_type: nil, location: nil) ⇒ Object
Check variable assignment.
-
#check_call(function_name, arguments, location: nil) ⇒ Object
Check a function call.
-
#check_function(function_info, body_lines) ⇒ Object
Check a complete function.
-
#check_function_legacy(method_ir) ⇒ Object
Check function without SMT (legacy).
-
#check_interface(interface_ir) ⇒ Object
Check interface implementation.
-
#check_method_with_smt(method_ir) ⇒ Object
Check a method definition using SMT solver.
-
#check_operator(left_type, operator, right_type, location: nil) ⇒ Object
Check operator usage.
-
#check_program(ir_program) ⇒ Object
Check an IR program using SMT-based type checking.
-
#check_program_legacy(ir_program) ⇒ Object
Legacy program check (without SMT).
-
#check_property_access(receiver_type, property_name, location: nil) ⇒ Object
Check property access.
-
#check_return(value, expected_type, location: nil) ⇒ Object
Check a return statement.
-
#check_statement(line, location: nil) ⇒ Object
Check a statement.
-
#diagnostics ⇒ Object
Get all diagnostics.
-
#infer_param_type(param) ⇒ Object
Infer parameter type from annotation.
-
#initialize(use_smt: true) ⇒ TypeChecker
constructor
A new instance of TypeChecker.
-
#known_type?(type_name) ⇒ Boolean
Check if a type name is known.
-
#narrow_in_conditional(condition, then_scope, else_scope = nil) ⇒ Object
Handle conditional type narrowing.
-
#register_alias(name, definition) ⇒ Object
Register a type alias.
-
#register_function(name, params:, return_type:) ⇒ Object
Register a function signature.
-
#reset ⇒ Object
Clear all state.
-
#resolve_type(type_name) ⇒ Object
Resolve type alias.
-
#subtype_with_smt?(subtype, supertype) ⇒ Boolean
SMT-based subtype checking.
-
#to_smt_type(type) ⇒ Object
Convert to SMT type.
-
#validate_type(type_node) ⇒ Object
Validate a type node is well-formed.
Constructor Details
#initialize(use_smt: true) ⇒ TypeChecker
Returns a new instance of TypeChecker.
174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/t_ruby/type_checker.rb', line 174 def initialize(use_smt: true) @errors = [] @warnings = [] @hierarchy = TypeHierarchy.new @inferencer = TypeInferencer.new @function_signatures = {} @type_aliases = {} @current_scope = TypeScope.new @flow_context = FlowContext.new @use_smt = use_smt @smt_solver = SMT::ConstraintSolver.new if use_smt @smt_inference_engine = SMT::TypeInferenceEngine.new if use_smt end |
Instance Attribute Details
#errors ⇒ Object (readonly)
Returns the value of attribute errors.
172 173 174 |
# File 'lib/t_ruby/type_checker.rb', line 172 def errors @errors end |
#hierarchy ⇒ Object (readonly)
Returns the value of attribute hierarchy.
172 173 174 |
# File 'lib/t_ruby/type_checker.rb', line 172 def hierarchy @hierarchy end |
#inferencer ⇒ Object (readonly)
Returns the value of attribute inferencer.
172 173 174 |
# File 'lib/t_ruby/type_checker.rb', line 172 def inferencer @inferencer end |
#smt_solver ⇒ Object (readonly)
Returns the value of attribute smt_solver.
172 173 174 |
# File 'lib/t_ruby/type_checker.rb', line 172 def smt_solver @smt_solver end |
#use_smt ⇒ Object (readonly)
Returns the value of attribute use_smt.
172 173 174 |
# File 'lib/t_ruby/type_checker.rb', line 172 def use_smt @use_smt end |
#warnings ⇒ Object (readonly)
Returns the value of attribute warnings.
172 173 174 |
# File 'lib/t_ruby/type_checker.rb', line 172 def warnings @warnings end |
Instance Method Details
#check_assignment(variable, value, declared_type: nil, location: nil) ⇒ Object
Check variable assignment
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/t_ruby/type_checker.rb', line 384 def check_assignment(variable, value, declared_type: nil, location: nil) value_type = infer_type(value) if declared_type unless type_compatible?(value_type, declared_type) add_error( message: "Cannot assign #{value_type} to variable of type #{declared_type}", expected: declared_type, actual: value_type, location: location ) return false end end @current_scope.define(variable, declared_type || value_type) true end |
#check_call(function_name, arguments, location: nil) ⇒ Object
Check a function call
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/t_ruby/type_checker.rb', line 318 def check_call(function_name, arguments, location: nil) signature = @function_signatures[function_name] unless signature add_warning("Unknown function: #{function_name}", location) return nil end params = signature[:params] # Check argument count if arguments.length != params.length add_error( message: "Wrong number of arguments for #{function_name}", expected: "#{params.length} arguments", actual: "#{arguments.length} arguments", location: location ) return nil end # Check each argument type params.each_with_index do |param, idx| next unless param[:type] arg = arguments[idx] arg_type = infer_type(arg) next unless arg_type unless type_compatible?(arg_type, param[:type]) add_error( message: "Type mismatch in argument '#{param[:name]}' of #{function_name}", expected: param[:type], actual: arg_type, suggestion: suggest_conversion(arg_type, param[:type]), location: location ) end end signature[:return_type] end |
#check_function(function_info, body_lines) ⇒ Object
Check a complete function
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 |
# File 'lib/t_ruby/type_checker.rb', line 504 def check_function(function_info, body_lines) @current_scope = TypeScope.new # Register parameters in scope function_info[:params].each do |param| @current_scope.define(param[:name], param[:type] || "Object") end # Register function signature register_function( function_info[:name], params: function_info[:params], return_type: function_info[:return_type] ) # Check body (simplified - real implementation would parse AST) body_lines.each_with_index do |line, idx| check_statement(line, location: "line #{idx + 1}") end end |
#check_function_legacy(method_ir) ⇒ Object
Check function without SMT (legacy)
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
# File 'lib/t_ruby/type_checker.rb', line 619 def check_function_legacy(method_ir) @current_scope = TypeScope.new # Register parameters method_ir.params.each do |param| param_type = infer_param_type(param) @current_scope.define(param.name, param_type) end # Register function signature register_function( method_ir.name, params: method_ir.params.map { |p| { name: p.name, type: infer_param_type(p) } }, return_type: method_ir.return_type&.to_s || "Object" ) end |
#check_interface(interface_ir) ⇒ Object
Check interface implementation
242 243 244 245 246 247 248 249 |
# File 'lib/t_ruby/type_checker.rb', line 242 def check_interface(interface_ir) interface_ir.members.each do |member| # Validate member type signature if member.type_signature validate_type(member.type_signature) end end end |
#check_method_with_smt(method_ir) ⇒ Object
Check a method definition using SMT solver
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/t_ruby/type_checker.rb', line 214 def check_method_with_smt(method_ir) result = @smt_inference_engine.infer_method(method_ir) if result[:success] # Store inferred types @function_signatures[method_ir.name] = { params: method_ir.params.map do |p| { name: p.name, type: result[:params][p.name] || infer_param_type(p) } end, return_type: result[:return_type] } else # Add errors from SMT solver result[:errors]&.each do |error| add_error( message: "Type inference error in #{method_ir.name}: #{error}", location: method_ir.location ) end end result end |
#check_operator(left_type, operator, right_type, location: nil) ⇒ Object
Check operator usage
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/t_ruby/type_checker.rb', line 426 def check_operator(left_type, operator, right_type, location: nil) # Arithmetic operators if %w[+ - * / %].include?(operator) if left_type == "String" && operator == "+" unless right_type == "String" add_error( message: "Cannot concatenate String with #{right_type}", expected: "String", actual: right_type, suggestion: "Use .to_s to convert to String", location: location ) end return "String" end unless numeric_type?(left_type) && numeric_type?(right_type) add_error( message: "Operator '#{operator}' requires numeric operands", expected: "Numeric", actual: "#{left_type} #{operator} #{right_type}", location: location ) return nil end # Result type depends on operands return "Float" if left_type == "Float" || right_type == "Float" return "Integer" end # Comparison operators if %w[== != < > <= >=].include?(operator) return "Boolean" end # Logical operators if %w[&& ||].include?(operator) return right_type # Short-circuit returns right operand type end nil end |
#check_program(ir_program) ⇒ Object
Check an IR program using SMT-based type checking
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/t_ruby/type_checker.rb', line 189 def check_program(ir_program) return check_program_legacy(ir_program) unless @use_smt @errors = [] @warnings = [] ir_program.declarations.each do |decl| case decl when IR::TypeAlias register_alias(decl.name, decl.definition) when IR::Interface check_interface(decl) when IR::MethodDef check_method_with_smt(decl) end end { success: @errors.empty?, errors: @errors, warnings: @warnings } end |
#check_program_legacy(ir_program) ⇒ Object
Legacy program check (without SMT)
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 |
# File 'lib/t_ruby/type_checker.rb', line 596 def check_program_legacy(ir_program) @errors = [] @warnings = [] ir_program.declarations.each do |decl| case decl when IR::TypeAlias register_alias(decl.name, decl.definition) when IR::Interface check_interface(decl) when IR::MethodDef check_function_legacy(decl) end end { success: @errors.empty?, errors: @errors, warnings: @warnings } end |
#check_property_access(receiver_type, property_name, location: nil) ⇒ Object
Check property access
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/t_ruby/type_checker.rb', line 404 def check_property_access(receiver_type, property_name, location: nil) # Known properties for common types known_properties = { "String" => %w[length size empty? chars bytes], "Array" => %w[length size first last empty? count], "Hash" => %w[keys values size empty? length], "Integer" => %w[abs to_s to_f even? odd? positive? negative?], "Float" => %w[abs to_s to_i ceil floor round] } properties = known_properties[receiver_type] return nil unless properties unless properties.include?(property_name) add_warning("Property '#{property_name}' may not exist on type #{receiver_type}", location) end # Return expected type for known properties infer_property_type(receiver_type, property_name) end |
#check_return(value, expected_type, location: nil) ⇒ Object
Check a return statement
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/t_ruby/type_checker.rb', line 363 def check_return(value, expected_type, location: nil) return true unless expected_type actual_type = infer_type(value) return true unless actual_type unless type_compatible?(actual_type, expected_type) add_error( message: "Return type mismatch", expected: expected_type, actual: actual_type, suggestion: suggest_conversion(actual_type, expected_type), location: location ) return false end true end |
#check_statement(line, location: nil) ⇒ Object
Check a statement
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'lib/t_ruby/type_checker.rb', line 526 def check_statement(line, location: nil) line = line.strip # Return statement if line.match?(/^return\s+(.+)/) match = line.match(/^return\s+(.+)/) # Would need current function context for proper checking return end # Assignment if line.match?(/^(\w+)\s*=\s*(.+)/) match = line.match(/^(\w+)\s*=\s*(.+)/) check_assignment(match[1], match[2], location: location) return end # Method call if line.match?(/(\w+)\(([^)]*)\)/) match = line.match(/(\w+)\(([^)]*)\)/) args = match[2].split(",").map(&:strip) check_call(match[1], args, location: location) end end |
#diagnostics ⇒ Object
Get all diagnostics
569 570 571 572 |
# File 'lib/t_ruby/type_checker.rb', line 569 def diagnostics @errors.map { |e| e.respond_to?(:to_diagnostic) ? e.to_diagnostic : { type: :error, message: e.to_s } } + @warnings.map { |w| { type: :warning, message: w } } end |
#infer_param_type(param) ⇒ Object
Infer parameter type from annotation
582 583 584 585 586 587 588 589 590 591 592 593 |
# File 'lib/t_ruby/type_checker.rb', line 582 def infer_param_type(param) if param.type_annotation case param.type_annotation when IR::SimpleType param.type_annotation.name else param.type_annotation.to_s end else "Object" end end |
#known_type?(type_name) ⇒ Boolean
Check if a type name is known
575 576 577 578 579 |
# File 'lib/t_ruby/type_checker.rb', line 575 def known_type?(type_name) return true if %w[String Integer Float Boolean Array Hash Symbol void nil Object Numeric Enumerable].include?(type_name) return true if @type_aliases.key?(type_name) false end |
#narrow_in_conditional(condition, then_scope, else_scope = nil) ⇒ Object
Handle conditional type narrowing
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 |
# File 'lib/t_ruby/type_checker.rb', line 471 def narrow_in_conditional(condition, then_scope, else_scope = nil) # Parse type guards from condition if condition.match?(/(\w+)\.is_a\?\((\w+)\)/) match = condition.match(/(\w+)\.is_a\?\((\w+)\)/) var = match[1] type = match[2] # In then branch, variable is narrowed to the type then_scope.narrow(var, type) # In else branch, variable is NOT that type (can't narrow further without more info) end if condition.match?(/(\w+)\.nil\?/) match = condition.match(/(\w+)\.nil\?/) var = match[1] then_scope.narrow(var, "nil") # In else branch, variable is not nil else_scope&.narrow(var, remove_nil_from_type(@current_scope.lookup(var) || "Object")) end if condition.match?(/!(\w+)\.nil\?/) match = condition.match(/!(\w+)\.nil\?/) var = match[1] # Variable is not nil in then branch then_scope.narrow(var, remove_nil_from_type(@current_scope.lookup(var) || "Object")) else_scope&.narrow(var, "nil") end end |
#register_alias(name, definition) ⇒ Object
Register a type alias
313 314 315 |
# File 'lib/t_ruby/type_checker.rb', line 313 def register_alias(name, definition) @type_aliases[name] = definition end |
#register_function(name, params:, return_type:) ⇒ Object
Register a function signature
305 306 307 308 309 310 |
# File 'lib/t_ruby/type_checker.rb', line 305 def register_function(name, params:, return_type:) @function_signatures[name] = { params: params, return_type: return_type } end |
#reset ⇒ Object
Clear all state
557 558 559 560 561 562 563 564 565 566 |
# File 'lib/t_ruby/type_checker.rb', line 557 def reset @errors.clear @warnings.clear @function_signatures.clear @type_aliases.clear @current_scope = TypeScope.new @flow_context = FlowContext.new @smt_solver = SMT::ConstraintSolver.new if @use_smt @smt_inference_engine = SMT::TypeInferenceEngine.new if @use_smt end |
#resolve_type(type_name) ⇒ Object
Resolve type alias
552 553 554 |
# File 'lib/t_ruby/type_checker.rb', line 552 def resolve_type(type_name) @type_aliases[type_name] || type_name end |
#subtype_with_smt?(subtype, supertype) ⇒ Boolean
SMT-based subtype checking
278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/t_ruby/type_checker.rb', line 278 def subtype_with_smt?(subtype, supertype) return true if subtype == supertype if @use_smt sub = to_smt_type(subtype) sup = to_smt_type(supertype) @smt_solver.subtype?(sub, sup) else @hierarchy.subtype_of?(subtype.to_s, supertype.to_s) end end |
#to_smt_type(type) ⇒ Object
Convert to SMT type
291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/t_ruby/type_checker.rb', line 291 def to_smt_type(type) case type when String SMT::ConcreteType.new(type) when IR::SimpleType SMT::ConcreteType.new(type.name) when SMT::ConcreteType, SMT::TypeVar type else SMT::ConcreteType.new(type.to_s) end end |
#validate_type(type_node) ⇒ Object
Validate a type node is well-formed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/t_ruby/type_checker.rb', line 252 def validate_type(type_node) case type_node when IR::SimpleType # Check if type exists unless known_type?(type_node.name) add_warning("Unknown type: #{type_node.name}") end when IR::GenericType # Check base type and type args unless known_type?(type_node.base) add_warning("Unknown generic type: #{type_node.base}") end type_node.type_args.each { |t| validate_type(t) } when IR::UnionType type_node.types.each { |t| validate_type(t) } when IR::IntersectionType type_node.types.each { |t| validate_type(t) } when IR::NullableType validate_type(type_node.inner_type) when IR::FunctionType type_node.param_types.each { |t| validate_type(t) } validate_type(type_node.return_type) end end |