From fc55b842ddbaec3a6d08aa3f140e580b7f8f5946 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 05:27:30 +0000 Subject: [PATCH 1/3] Skip the variance validation of types without type params `DefinitionBuilder#validate_type_params` ran the variance calculator over every ancestor and every method type of every type being built, but without type params nothing can violate the variance: the ancestor validation iterates the (empty) params of the type, and the type params of the methods themselves are invariant, which `Result#compatible?` always accepts. Return early in that case. Most types have no type params, so this skips the whole calculation for most of the environment -- the validation was 12% of a whole-environment warmup of a large Rails application. Co-Authored-By: Claude Fable 5 --- lib/rbs/definition_builder.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index 715b417a9..61e5375b2 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -504,6 +504,11 @@ def source_location(source, decl) def validate_type_params(definition, ancestors:, methods:) type_params = definition.type_params_decl + # Without type params nothing can violate the variance: the ancestor validation + # iterates the (empty) params, and the type params of the methods themselves are + # invariant, which `Result#compatible?` always accepts + return if type_params.empty? + calculator = VarianceCalculator.new(builder: self) param_names = type_params.each.map(&:name) From c2ae88f65539cfb2be416f51a416310ef7f15b4b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 05:51:28 +0000 Subject: [PATCH 2/3] Report NoTypeFoundError with the name as written `DefinitionBuilder#validate_type_name` absolutized the name before raising, while the variance calculation reported undefined names in method types as they are written. Skipping the variance calculation for types without type params moved their detection to `validate_type_name`, turning messages like `Could not find voida` into `Could not find ::voida`. Raise with the as-written name instead, which also matches the location the message points at -- `Could not find A` for `extend Bar[A]`, where `::A` was reported before. Co-Authored-By: Claude Fable 5 --- lib/rbs/definition_builder.rb | 5 +++-- test/rbs/cli_test.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index 61e5375b2..fd6b61534 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -1053,9 +1053,10 @@ def validate_type_presence(type) end def validate_type_name(name, location) - name = name.absolute! unless name.absolute? - return if env.type_name?(env.normalize_type_name(name)) + absolute = name.absolute? ? name : name.absolute! + return if env.type_name?(env.normalize_type_name(absolute)) + # Report the name as it is written in the signature raise NoTypeFoundError.new(type_name: name, location: location) end end diff --git a/test/rbs/cli_test.rb b/test/rbs/cli_test.rb index 3d37fe9c0..e9f6672a0 100644 --- a/test/rbs/cli_test.rb +++ b/test/rbs/cli_test.rb @@ -402,7 +402,7 @@ module Bar[B] cli.run(["-I", dir, "validate"]) end - assert_include stdout.string, "a.rbs:2:13...2:14: Could not find ::A (RBS::NoTypeFoundError)" + assert_include stdout.string, "a.rbs:2:13...2:14: Could not find A (RBS::NoTypeFoundError)" end end end From a5458a83b1fba9a335bad3fee4ff60bc2df83315 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 07:03:49 +0000 Subject: [PATCH 3/3] Validate the type params of an entry once `ClassEntry#type_params` and `ModuleEntry#type_params` validated the consistency of the type params across the declarations on every call, renaming and comparing the param lists each time, and the definition builder reads `type_params` of the same entries over and over while resolving ancestors. Record a successful validation and skip it until `<<` adds another declaration; a failed validation is not recorded and raises again. Co-Authored-By: Claude Fable 5 --- lib/rbs/environment/class_entry.rb | 7 +++++++ lib/rbs/environment/module_entry.rb | 7 +++++++ sig/environment/class_entry.rbs | 2 ++ sig/environment/module_entry.rbs | 2 ++ 4 files changed, 18 insertions(+) diff --git a/lib/rbs/environment/class_entry.rb b/lib/rbs/environment/class_entry.rb index 73762330a..56366ee43 100644 --- a/lib/rbs/environment/class_entry.rb +++ b/lib/rbs/environment/class_entry.rb @@ -15,6 +15,7 @@ def initialize(name) def <<(context_decl) context_decls << context_decl @primary_decl = nil + @type_params_validated = nil self end @@ -50,6 +51,10 @@ def type_params end def validate_type_params + # The entry only changes with `<<`, which resets the memo -- a failed + # validation is not recorded and raises again + return if @type_params_validated + unless context_decls.empty? first_decl, *rest_decls = each_decl.to_a first_decl or raise @@ -63,6 +68,8 @@ def validate_type_params end end end + + @type_params_validated = true end def align_params(decl) diff --git a/lib/rbs/environment/module_entry.rb b/lib/rbs/environment/module_entry.rb index 1f65cf490..95db8eb14 100644 --- a/lib/rbs/environment/module_entry.rb +++ b/lib/rbs/environment/module_entry.rb @@ -14,6 +14,7 @@ def initialize(name) def <<(context_decl) context_decls << context_decl + @type_params_validated = nil self end @@ -72,6 +73,10 @@ def align_params(decl) end def validate_type_params + # The entry only changes with `<<`, which resets the memo -- a failed + # validation is not recorded and raises again + return if @type_params_validated + unless context_decls.empty? first_decl, *rest_decls = each_decl.to_a first_decl or raise @@ -85,6 +90,8 @@ def validate_type_params end end end + + @type_params_validated = true end end end diff --git a/sig/environment/class_entry.rbs b/sig/environment/class_entry.rbs index ff97cc5c1..00e73e541 100644 --- a/sig/environment/class_entry.rbs +++ b/sig/environment/class_entry.rbs @@ -18,6 +18,8 @@ module RBS @primary_decl: declaration? + @type_params_validated: bool? + def initialize: (TypeName) -> void def <<: (context_decl) -> self diff --git a/sig/environment/module_entry.rbs b/sig/environment/module_entry.rbs index c556b0979..3230a4b35 100644 --- a/sig/environment/module_entry.rbs +++ b/sig/environment/module_entry.rbs @@ -17,6 +17,8 @@ module RBS attr_reader context_decls: Array[context_decl] + @type_params_validated: bool? + def initialize: (TypeName) -> void def <<: (context_decl) -> self