Skip to content
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

Fix: Do not consider global Path in def parameter restriction as free variable #11862

Merged
merged 2 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 21 additions & 0 deletions spec/compiler/semantic/def_overload_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,27 @@ describe "Semantic: def overload" do
") { int32 }
end

it "does not consider global paths as free variables (1)" do
assert_error <<-CR, "undefined constant ::Foo"
def foo(x : ::Foo) forall Foo
end

foo(1)
CR
end

it "does not consider global paths as free variables (2)" do
assert_error <<-CR, "no overload matches"
class Foo
end

def foo(x : ::Foo) forall Foo
end

foo(1)
CR
end

it "prefers more specific overload than one with free variables" do
assert_type("
require \"prelude\"
Expand Down
4 changes: 1 addition & 3 deletions src/compiler/crystal/semantic/abstract_def_checker.cr
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,8 @@ class Crystal::AbstractDefChecker
end

def visit(node : Path)
if !node.global? && node.names.size == 1
if name = node.single_name?
# Check if it matches any of the generic type vars
name = node.names.first

type_var = @generic_type.type_vars[name]?
if type_var.is_a?(Var)
# Check that it's actually a type parameter on the base type
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ module Crystal
def free_var?(node : Path)
free_vars = @free_vars
return false unless free_vars

!node.global? && node.names.size == 1 && free_vars.includes?(node.names.first)
name = node.single_name?
!name.nil? && free_vars.includes?(name)
end

def free_var?(any)
Expand Down
34 changes: 12 additions & 22 deletions src/compiler/crystal/semantic/restrictions.cr
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,8 @@ module Crystal
# Match all concrete types first
free_var_count = other.types.count do |other_type|
other_type.is_a?(Path) &&
other_type.names.size == 1 &&
context.has_def_free_var?(other_type.names.first)
(first_name = other_type.single_name?) &&
context.has_def_free_var?(first_name)
end
if free_var_count > 1
other.raise "can't specify more than one free var in union restriction"
Expand All @@ -495,21 +495,18 @@ module Crystal
end

def restrict(other : Path, context)
single_name = other.names.size == 1
if single_name
first_name = other.names.first
if first_name = other.single_name?
if context.has_def_free_var?(first_name)
return context.set_free_var(first_name, self)
end
end

if single_name
if first_name
owner = context.instantiated_type

# Special case: if we have an *uninstantiated* generic type like Foo(X)
# and a restriction X, it matches, and we add X to the free vars.
if owner.is_a?(GenericType)
first_name = other.names.first
if owner.type_vars.includes?(first_name)
context.set_free_var(first_name, self)
return self
Expand All @@ -530,8 +527,7 @@ module Crystal
return restrict ident_type, context
end

if single_name
first_name = other.names.first
if first_name
if context.defining_type.type_var?(first_name)
return context.set_free_var(first_name, self)
end
Expand Down Expand Up @@ -653,8 +649,8 @@ module Crystal
# Match all concrete types first
free_vars, other_types = other.types.partition do |other_type|
other_type.is_a?(Path) &&
other_type.names.size == 1 &&
context.has_def_free_var?(other_type.names.first)
(first_name = other_type.single_name?) &&
context.has_def_free_var?(first_name)
end
if free_vars.size > 1
other.raise "can't specify more than one free var in union restriction"
Expand Down Expand Up @@ -871,17 +867,15 @@ module Crystal
return type_var
end
when Path
if other_type_var.names.size == 1
name = other_type_var.names.first

if first_name = other_type_var.single_name?
# If the free variable is already set to another
# number, there's no match
existing = context.get_free_var(name)
existing = context.get_free_var(first_name)
if existing && existing != type_var
return nil
end

context.set_free_var(name, type_var)
context.set_free_var(first_name, type_var)
return type_var
end
else
Expand Down Expand Up @@ -1091,9 +1085,7 @@ module Crystal
end

def restrict(other : Path, context)
single_name = other.names.size == 1
if single_name
first_name = other.names.first
if first_name = other.single_name?
if context.has_def_free_var?(first_name)
return context.set_free_var(first_name, self)
end
Expand All @@ -1105,9 +1097,7 @@ module Crystal
return self
end
else
single_name = other.names.size == 1
if single_name
first_name = other.names.first
if first_name = other.single_name?
if context.defining_type.type_var?(first_name)
return context.set_free_var(first_name, self)
else
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,11 @@ module Crystal
names.size == 1 && names.first == name
end

# Returns this path's name if it has only one part and is not global
def single_name?
names.first if names.size == 1 && !global?
end

def clone_without_location
ident = Path.new(@names.clone, @global)
ident
Expand Down