-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compiler: remove duplicate instance vars once we know them all #11995
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -155,6 +155,8 @@ struct Crystal::TypeDeclarationProcessor | |
|
||
process_instance_vars_declarations | ||
|
||
remove_duplicate_instance_vars_declarations | ||
|
||
# Check that instance vars that weren't initialized in an initialize, | ||
# but a superclass does initialize then, are nilable, and if not | ||
# give an error | ||
|
@@ -272,20 +274,14 @@ struct Crystal::TypeDeclarationProcessor | |
if supervar && supervar.owner != owner | ||
# Redeclaring a variable with the same type is OK | ||
unless supervar.type.same?(type_decl.type) | ||
raise TypeException.new("instance variable '#{name}' of #{supervar.owner}, with #{owner} < #{supervar.owner}, is already declared as #{supervar.type} (trying to re-declare as #{type_decl.type})", type_decl.location) | ||
raise TypeException.new("instance variable '#{name}' of #{supervar.owner}, with #{owner} < #{supervar.owner}, is already declared as #{supervar.type} (trying to re-declare it in #{owner} as #{type_decl.type})", type_decl.location) | ||
end | ||
|
||
# Reject annotations to existing instance var | ||
type_decl.annotations.try &.each do |_, ann| | ||
ann.raise "can't annotate #{name} in #{owner} because it was first defined in #{supervar.owner}" | ||
end | ||
else | ||
owner.all_subclasses.each do |subclass| | ||
if subclass.is_a?(InstanceVarContainer) && (subclass_var = subclass.instance_vars[name]?) | ||
raise TypeException.new("instance variable '#{name}' of #{subclass_var.owner} is already defined in #{owner}", subclass_var.location || type_decl.location) | ||
end | ||
end | ||
|
||
declare_meta_type_var(owner.instance_vars, owner, name, type_decl, instance_var: true, check_nilable: !owner.module?) | ||
remove_error owner, name | ||
|
||
|
@@ -391,12 +387,6 @@ struct Crystal::TypeDeclarationProcessor | |
supervar = owner.lookup_instance_var?(name) | ||
return if supervar | ||
|
||
owner.all_subclasses.each do |subclass| | ||
if subclass.is_a?(InstanceVarContainer) && (subclass_var = subclass.instance_vars[name]?) | ||
raise TypeException.new("instance variable '#{name}' of #{subclass_var.owner} is already defined in #{owner}", subclass_var.location || type_info.location) | ||
end | ||
end | ||
|
||
case owner | ||
when NonGenericClassType | ||
type = type_info.type | ||
|
@@ -631,6 +621,40 @@ struct Crystal::TypeDeclarationProcessor | |
nil | ||
end | ||
|
||
private def remove_duplicate_instance_vars_declarations | ||
# All the types that we checked for duplicate variables | ||
duplicates_checked = Set(Type).new | ||
remove_duplicate_instance_vars_declarations(@program, duplicates_checked) | ||
end | ||
|
||
private def remove_duplicate_instance_vars_declarations(type : Type, duplicates_checked : Set(Type)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity, wouldn't it make sense just to use defaults and avoid the other definition? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think using defaults makes sense. It's not like I'd like to call this with one of the arguments and not the other. But... if you feel like it, feel free to adapt this PR to your taste :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is fine, I was just curious. And I just saw a resolved previous comment about this same thing, sorry for the nagging! |
||
return unless duplicates_checked.add?(type) | ||
|
||
# If a class has an instance variable that already exists in a superclass, remove it. | ||
# Ideally we should process instance variables in a top-down fashion, but it's tricky | ||
# with modules and multiple-inheritance. Removing duplicates at the end is maybe | ||
# a bit more expensive, but it's simpler. | ||
if type.is_a?(InstanceVarContainer) && type.class? && !type.instance_vars.empty? | ||
type.instance_vars.reject! do |name, ivar| | ||
supervar = type.superclass.try &.lookup_instance_var?(name) | ||
if supervar && supervar.type != ivar.type | ||
message = "instance variable '#{name}' of #{supervar.owner}, with #{type} < #{supervar.owner}, is already declared as #{supervar.type} (trying to re-declare it in #{type} as #{ivar.type})" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe adding the reference to re-declaring in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But the conflict is that the variable was redeclared in the class because if the module inclusion. In any case are you fine with continuing this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think this is a gread solution. It restores backwards compatibility and fixes the original issue. We were discussing the benefits of more strict behaviour, but that would be something for later (if at all). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a quick comment: I think that If we want a stricter behavior, that should be discussed now: by enabling this behavior (which wasn't really working before) we are making it part of the language. Removing it later is a major breaking change. This said, I think we agree we want this, and not having this might be a discussion worth having only for 2.0. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why I think this is the correct thing to do: instance variables only live on classes, because you can't instantiate modules. If an instance variable is "declared" in a module that's later included in the middle of a type hierarchy, and the type matches, I don't see why it would be a problem. It was a problem before only because a bug existed around this. But if the bug didn't exist, I can't think of another semantic for this. Why would it not compile? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just the 'ol saying: with great power comes great responsibility |
||
location = ivar.location || type.locations.try &.first | ||
if location | ||
raise TypeException.new(message) | ||
else | ||
raise TypeException.new(message) | ||
end | ||
end | ||
supervar | ||
end | ||
end | ||
|
||
type.types?.try &.each_value do |nested_type| | ||
remove_duplicate_instance_vars_declarations(nested_type, duplicates_checked) | ||
end | ||
end | ||
|
||
private def check_nilable_instance_vars | ||
@nilable_instance_vars.each do |owner, vars| | ||
vars.each do |name, info| | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not very happy about the duplication of
owner
here. Type names can be quite a mouthful, so it would be nice to reduce their usage to the minimum.Maybe we can rephrase this error message to be more concise?
A similar rephrasing could be applied to the other message.