From acb9a411c28245f6b5b15049edc06c8f33bcba3a Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 8 Sep 2016 12:20:44 -0300 Subject: [PATCH] Add private types. Fixes #2950 --- .../{private_def_spec.cr => private_spec.cr} | 2 +- spec/compiler/semantic/private_def_spec.cr | 139 ------- spec/compiler/semantic/private_spec.cr | 353 ++++++++++++++++++ src/compiler/crystal/macros/interpreter.cr | 2 +- src/compiler/crystal/semantic/call_error.cr | 16 + .../class_vars_initializer_visitor.cr | 2 +- .../instance_vars_initializer_visitor.cr | 2 +- src/compiler/crystal/semantic/new.cr | 3 + src/compiler/crystal/semantic/path_lookup.cr | 52 ++- src/compiler/crystal/semantic/restrictions.cr | 21 +- .../crystal/semantic/top_level_visitor.cr | 74 +++- src/compiler/crystal/semantic/type_lookup.cr | 9 +- src/compiler/crystal/syntax/ast.cr | 10 + src/compiler/crystal/tools/doc/generator.cr | 1 + src/compiler/crystal/types.cr | 8 + 15 files changed, 507 insertions(+), 187 deletions(-) rename spec/compiler/codegen/{private_def_spec.cr => private_spec.cr} (97%) delete mode 100644 spec/compiler/semantic/private_def_spec.cr create mode 100644 spec/compiler/semantic/private_spec.cr diff --git a/spec/compiler/codegen/private_def_spec.cr b/spec/compiler/codegen/private_spec.cr similarity index 97% rename from spec/compiler/codegen/private_def_spec.cr rename to spec/compiler/codegen/private_spec.cr index acd7eb4e9169..f51552d7b176 100644 --- a/spec/compiler/codegen/private_def_spec.cr +++ b/spec/compiler/codegen/private_spec.cr @@ -1,7 +1,7 @@ require "../../spec_helper" require "tempfile" -describe "Codegen: private def" do +describe "Codegen: private" do it "codegens private def in same file" do compiler = Compiler.new sources = [ diff --git a/spec/compiler/semantic/private_def_spec.cr b/spec/compiler/semantic/private_def_spec.cr deleted file mode 100644 index dc3966ac2b14..000000000000 --- a/spec/compiler/semantic/private_def_spec.cr +++ /dev/null @@ -1,139 +0,0 @@ -require "../../spec_helper" - -describe "Semantic: private def" do - it "doesn't find private def in another file" do - expect_raises Crystal::TypeException, "undefined local variable or method 'foo'" do - compiler = Compiler.new - sources = [ - Compiler::Source.new("foo.cr", %( - private def foo - 1 - end - )), - Compiler::Source.new("bar.cr", %( - foo - )), - ] - compiler.no_codegen = true - compiler.prelude = "empty" - compiler.compile sources, "output" - end - end - - it "finds private def in same file" do - compiler = Compiler.new - sources = [ - Compiler::Source.new("foo.cr", %( - private def foo - 1 - end - - foo - )), - ] - compiler.no_codegen = true - compiler.prelude = "empty" - compiler.compile sources, "output" - end - - it "finds private def in same file that invokes another def" do - compiler = Compiler.new - sources = [ - Compiler::Source.new("foo.cr", %( - def bar - 2 - end - - private def foo - bar - end - - foo - )), - ] - compiler.no_codegen = true - compiler.prelude = "empty" - compiler.compile sources, "output" - end - - it "types private def correctly" do - assert_type(%( - private def foo - 1 - end - - def foo - 'a' - end - - foo - )) { int32 } - end - - it "doesn't find private macro in another file" do - expect_raises Crystal::TypeException, "undefined local variable or method 'foo'" do - compiler = Compiler.new - sources = [ - Compiler::Source.new("foo.cr", %( - private macro foo - 1 - end - )), - Compiler::Source.new("bar.cr", %( - foo - )), - ] - compiler.no_codegen = true - compiler.prelude = "empty" - compiler.compile sources, "output" - end - end - - it "finds private macro in same file" do - compiler = Compiler.new - sources = [ - Compiler::Source.new("foo.cr", %( - private macro foo - 1 - end - - foo - )), - ] - compiler.no_codegen = true - compiler.prelude = "empty" - compiler.compile sources, "output" - end - - it "finds private macro in same file, invoking from another macro (#1265)" do - compiler = Compiler.new - sources = [ - Compiler::Source.new("foo.cr", %( - private macro foo - 1 - end - - macro bar - foo - end - - bar - )), - ] - compiler.no_codegen = true - compiler.prelude = "empty" - compiler.compile sources, "output" - end - - it "finds private def when invoking from inside macro (#2082)" do - assert_type(%( - private def foo - 42 - end - - {% begin %} - foo - {% end %} - )) { int32 } - end -end diff --git a/spec/compiler/semantic/private_spec.cr b/spec/compiler/semantic/private_spec.cr new file mode 100644 index 000000000000..ec18cbb15345 --- /dev/null +++ b/spec/compiler/semantic/private_spec.cr @@ -0,0 +1,353 @@ +require "../../spec_helper" + +describe "Semantic: private" do + it "doesn't find private def in another file" do + expect_raises Crystal::TypeException, "undefined local variable or method 'foo'" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + private def foo + 1 + end + )), + Compiler::Source.new("bar.cr", %( + foo + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + end + + it "finds private def in same file" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + private def foo + 1 + end + + foo + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + + it "finds private def in same file that invokes another def" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + def bar + 2 + end + + private def foo + bar + end + + foo + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + + it "types private def correctly" do + assert_type(%( + private def foo + 1 + end + + def foo + 'a' + end + + foo + )) { int32 } + end + + it "doesn't find private macro in another file" do + expect_raises Crystal::TypeException, "undefined local variable or method 'foo'" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + private macro foo + 1 + end + )), + Compiler::Source.new("bar.cr", %( + foo + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + end + + it "finds private macro in same file" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + private macro foo + 1 + end + + foo + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + + it "finds private macro in same file, invoking from another macro (#1265)" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + private macro foo + 1 + end + + macro bar + foo + end + + bar + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + + it "finds private def when invoking from inside macro (#2082)" do + assert_type(%( + private def foo + 42 + end + + {% begin %} + foo + {% end %} + )) { int32 } + end + + it "doesn't find private class in another file" do + expect_raises Crystal::TypeException, "undefined constant Foo" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + private class Foo + end + )), + Compiler::Source.new("bar.cr", %( + Foo + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + end + + it "finds private type in same file" do + compiler = Compiler.new + sources = [ + Compiler::Source.new("foo.cr", %( + private class Foo + def foo + 1 + end + end + + Foo.new.foo + )), + ] + compiler.no_codegen = true + compiler.prelude = "empty" + compiler.compile sources, "output" + end + + it "can use types in private type" do + assert_type(%( + private class Foo + def initialize(@x : Int32) + end + + def foo + @x + 20 + end + end + + Foo.new(10).foo + )) { int32 } + end + + it "can use class var initializer in private type" do + assert_type(%( + private class Foo + @@x = 1 + + def self.x + @@x + end + end + + Foo.x + ), inject_primitives: false) { int32 } + end + + it "can use instance var initializer in private type" do + assert_type(%( + private class Foo + @x = 1 + + def x + @x + end + end + + Foo.new.x + ), inject_primitives: false) { int32 } + end + + it "finds private class in macro expansion" do + assert_type(%( + private class Foo + @x = 1 + + def x + @x + end + end + + macro foo + Foo.new.x + end + + foo + ), inject_primitives: false) { int32 } + end + + it "doesn't find private class from outside namespace" do + assert_error %( + class Foo + private class Bar + end + end + + Foo::Bar + ), + "private constant Foo::Bar referenced" + end + + it "doesn't find private module from outside namespace" do + assert_error %( + class Foo + private module Bar + end + end + + Foo::Bar + ), + "private constant Foo::Bar referenced" + end + + it "doesn't find private enum from outside namespace" do + assert_error %( + class Foo + private enum Bar + A + end + end + + Foo::Bar + ), + "private constant Foo::Bar referenced" + end + + it "doesn't find private alias from outside namespace" do + assert_error %( + class Foo + private alias Bar = Int32 + end + + Foo::Bar + ), + "private constant Foo::Bar referenced" + end + + it "doesn't find private lib from outside namespace" do + assert_error %( + class Foo + private lib LibBar + end + end + + Foo::LibBar + ), + "private constant Foo::LibBar referenced" + end + + it "doesn't find private constant from outside namespace" do + assert_error %( + class Foo + private Bar = 1 + end + + Foo::Bar + ), + "private constant Foo::Bar referenced" + end + + it "finds private type from inside namespace" do + assert_type(%( + class Foo + private class Bar + def self.foo + 1 + end + end + + x = Bar.foo + end + + x + )) { int32 } + end + + it "finds private type from inside namespace in subclass" do + assert_type(%( + class Foo + private class Bar + def self.foo + 1 + end + end + end + + class Foo2 < Foo + x = Bar.foo + end + + x + )) { int32 } + end + + it "gives private constant error in macro" do + assert_error %( + class Foo + private class Bar + end + end + + {{ Foo::Bar }} + ), + "private constant Foo::Bar referenced" + end +end diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr index 342c601423e2..57e16d0625b5 100644 --- a/src/compiler/crystal/macros/interpreter.cr +++ b/src/compiler/crystal/macros/interpreter.cr @@ -378,7 +378,7 @@ module Crystal end unless matched_type - node.raise "undefined constant #{node}" + node.raise_undefined_constant(@path_lookup) end case matched_type diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index cd6ecf309697..b348687e25e2 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -20,6 +20,22 @@ class Crystal::ASTNode end end +class Crystal::Path + def raise_undefined_constant(type) + private_const = type.lookup_path(self, include_private: true) + if private_const + self.raise("private constant #{private_const} referenced") + end + + similar_name = type.lookup_similar_path(self) + if similar_name + self.raise("undefined constant #{self} #{type.program.colorize("(did you mean '#{similar_name}')").yellow.bold}") + else + self.raise("undefined constant #{self}") + end + end +end + class Crystal::Call def raise_matches_not_found(owner, def_name, arg_types, named_args_types, matches = nil) # Special case: Foo+:Class#new diff --git a/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr b/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr index fc6eecca0855..ed05d23ae1bf 100644 --- a/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr +++ b/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr @@ -120,7 +120,7 @@ module Crystal when TypeDeclaration node.var.is_a?(ClassVar) when FileNode, Expressions, ClassDef, ModuleDef, EnumDef, Alias, Include, Extend, LibDef, Def, Macro, Call, Require, - MacroExpression, MacroIf, MacroFor + MacroExpression, MacroIf, MacroFor, VisibilityModifier true else false diff --git a/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr b/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr index 89317a4e1481..bdb98f06c873 100644 --- a/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr +++ b/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr @@ -33,7 +33,7 @@ class Crystal::InstanceVarsInitializerVisitor < Crystal::SemanticVisitor when TypeDeclaration node.var.is_a?(InstanceVar) when FileNode, Expressions, ClassDef, ModuleDef, Alias, Include, Extend, LibDef, Def, Macro, Call, Require, - MacroExpression, MacroIf, MacroFor + MacroExpression, MacroIf, MacroFor, VisibilityModifier true else false diff --git a/src/compiler/crystal/semantic/new.cr b/src/compiler/crystal/semantic/new.cr index d1a6675c7b27..b1339207dfcb 100644 --- a/src/compiler/crystal/semantic/new.cr +++ b/src/compiler/crystal/semantic/new.cr @@ -10,6 +10,9 @@ module Crystal # We also need to define empty `new` methods for types # that don't have any `initialize` methods. define_default_new(self) + file_modules.each_value do |file_module| + define_default_new(file_module) + end end def define_default_new(type) diff --git a/src/compiler/crystal/semantic/path_lookup.cr b/src/compiler/crystal/semantic/path_lookup.cr index 2ee0c1a099b4..10f871413369 100644 --- a/src/compiler/crystal/semantic/path_lookup.cr +++ b/src/compiler/crystal/semantic/path_lookup.cr @@ -21,23 +21,30 @@ module Crystal # # Returns `nil` if the path can't be found. # + # *include_private* controls whether private types are found inside + # other types (when doing Foo::Bar, Bar won't be found if it's private). + # + # *location* can be passed and is the location where the lookup happens, + # and is useful to find file-private types. + # # The result can be an `ASTNode` in the case the path denotes a type variable # whose variable is an `ASTNode`. One such example is the `N` of `StaticArray(T, N)` # for some instantiated `StaticArray`. # # If the path is global (for example ::Foo::Bar), the search starts at # the top level. - def lookup_path(path : Path, lookup_in_namespace = true) : Type | ASTNode | Nil - (path.global? ? program : self).lookup_path(path.names, lookup_in_namespace: lookup_in_namespace) + def lookup_path(path : Path, lookup_in_namespace = true, include_private = false, location = path.location) : Type | ASTNode | Nil + location = nil if path.global? + (path.global? ? program : self).lookup_path(path.names, lookup_in_namespace, include_private, location) end # ditto - def lookup_path(names : Array(String), lookup_in_namespace = true) : Type | ASTNode | Nil + def lookup_path(names : Array(String), lookup_in_namespace = true, include_private = false, location = nil) : Type | ASTNode | Nil type = self names.each_with_index do |name, i| # The search must continue in the namespace only for the first path # item: for subsequent path items only the parents must be looked up - type = type.lookup_path_item(name, lookup_in_namespace: lookup_in_namespace && i == 0) + type = type.lookup_path_item(name, lookup_in_namespace: lookup_in_namespace && i == 0, include_private: i == 0 || include_private, location: location) return unless type # Stop if this is the last name @@ -58,28 +65,47 @@ module Crystal # type's namespace. This parameter is useful because when writing # `Foo::Bar::Baz`, `Foo` should be searched in enclosing namespaces, # but `Bar` and `Baz` not. - def lookup_path_item(name : String, lookup_in_namespace) : Type | ASTNode | Nil + def lookup_path_item(name : String, lookup_in_namespace, include_private, location) : Type | ASTNode | Nil # First search in our types type = types?.try &.[name]? - return type if type + if type + if type.private? && !include_private + return nil + end + + return type + end # Then try out parents, but don't search in our parents namespace parents.try &.each do |parent| - match = parent.lookup_path_item(name, lookup_in_namespace: false) + match = parent.lookup_path_item(name, lookup_in_namespace: false, include_private: include_private, location: location) return match if match end # Try our namespace, unless we are the top-level if lookup_in_namespace && self != program - return namespace.lookup_path_item(name, lookup_in_namespace) + return namespace.lookup_path_item(name, lookup_in_namespace, include_private, location) end nil end end + class Program + def lookup_path_item(name : String, lookup_in_namespace, include_private, location) + # Check if there's a private type in location + if location && (original_filename = location.original_filename) && + (file_module = file_module?(original_filename)) && + (item = file_module.types[name]?) + return item + end + + super + end + end + module GenericType - def lookup_path_item(name : String, lookup_in_namespace) + def lookup_path_item(name : String, lookup_in_namespace, include_private, location) # If we are Foo(T) and somebody looks up the type T, we return `nil` because we don't # know what type T is, and we don't want to continue search in the namespace if type_vars.includes?(name) @@ -90,7 +116,7 @@ module Crystal end class GenericInstanceType - def lookup_path_item(name : String, lookup_in_namespace) + def lookup_path_item(name : String, lookup_in_namespace, include_private, location) # Check if *name* is a type variable if type_var = type_vars[name]? if type_var.is_a?(Var) @@ -99,19 +125,19 @@ module Crystal type_var end else - generic_type.lookup_path_item(name, lookup_in_namespace) + generic_type.lookup_path_item(name, lookup_in_namespace, include_private, location) end end end class UnionType - def lookup_path_item(name : String, lookup_in_namespace) + def lookup_path_item(name : String, lookup_in_namespace, include_private, location) # Union type does not currently inherit GenericClassInstanceType, # so we check if *name* is the only type variable of Union(*T) if name == "T" return program.tuple_of(union_types) end - program.lookup_path_item(name, lookup_in_namespace) + program.lookup_path_item(name, lookup_in_namespace, include_private, location) end end diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index b9563ac89e9c..32f3c08846d5 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -361,18 +361,21 @@ module Crystal ident_type = context.get_free_var(other.names.first) end + had_ident_type = !!ident_type ident_type ||= context.defining_type.lookup_path other if ident_type - restrict ident_type, context - elsif single_name - if Parser.free_var_name?(other.names.first) - context.set_free_var(other.names.first, self) - else - other.raise "undefined constant #{other}" - end - else + return restrict ident_type, context + end + + if single_name && Parser.free_var_name?(other.names.first) + return context.set_free_var(other.names.first, self) + end + + if had_ident_type other.raise "undefined constant #{other}" + else + other.raise_undefined_constant(context.defining_type) end end @@ -830,7 +833,7 @@ module Crystal if Parser.free_var_name?(other.names.first) return context.set_free_var(other.names.first, self) else - other.raise "undefined constant #{other}" + other.raise_undefined_constant(context.defining_type) end end end diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 605da76fa446..2e4461afa975 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -43,7 +43,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def visit(node : ClassDef) check_outside_exp node, "declare class" - scope, name = lookup_type_def_name(node.name) + scope, name = lookup_type_def_name(node) type = scope.types[name]? @@ -88,6 +88,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor type.struct = node.struct? end + type.private = true if node.visibility.private? + node_superclass = node.superclass if node_superclass if type_vars = node.type_vars @@ -136,7 +138,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end end - scope.types[name] = type if created_new_type + scope.types[name] = type node.resolved_type = type attach_doc type, node @@ -156,7 +158,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def visit(node : ModuleDef) check_outside_exp node, "declare module" - scope, name = lookup_type_def_name(node.name) + scope, name = lookup_type_def_name(node) type = scope.types[name]? if type @@ -179,6 +181,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor scope.types[name] = type end + type.private = true if node.visibility.private? + node.resolved_type = type attach_doc type, node @@ -208,6 +212,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor attach_doc alias_type, node current_type.types[node.name] = alias_type + alias_type.private = true if node.visibility.private? + node.resolved_type = alias_type false @@ -335,17 +341,20 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor link_attributes = process_link_attributes - type = current_type.types[node.name]? + scope = current_type_scope(node) + + type = scope.types[node.name]? if type node.raise "#{node.name} is not a lib" unless type.is_a?(LibType) else - check_not_free_var_name(current_type, node.name, node) + check_not_free_var_name(scope, node.name, node) - type = LibType.new @program, current_type, node.name - current_type.types[node.name] = type + type = LibType.new @program, scope, node.name + scope.types[node.name] = type end node.resolved_type = type + type.private = true if node.visibility.private? type.add_link_attributes(link_attributes) pushing_type(type) do @@ -405,7 +414,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor attributes = check_valid_attributes node, ValidEnumDefAttributes, "enum" attributes_doc = attributes_doc() - scope, name = lookup_type_def_name(node.name) + scope, name = lookup_type_def_name(node) enum_type = scope.types[name]? if enum_type @@ -431,6 +440,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor EnumType.new(@program, scope, name, enum_base_type, is_flags) end + enum_type.private = true if node.visibility.private? + node.resolved_type = enum_type attach_doc enum_type, node @@ -558,17 +569,20 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor check_outside_exp node, "declare constant" @exp_nest += 1 - type = current_type.types[target.names.first]? + scope = current_type_scope(target) + + type = scope.types[target.names.first]? if type target.raise "already initialized constant #{type}" end - check_not_free_var_name(current_type, target.names.first, target) + check_not_free_var_name(scope, target.names.first, target) - const = Const.new(@program, current_type, target.names.first, value) + const = Const.new(@program, scope, target.names.first, value) + const.private = true if target.visibility.private? attach_doc const, node - current_type.types[target.names.first] = const + scope.types[target.names.first] = const target.target_const = const end @@ -582,8 +596,22 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.exp.visibility = node.modifier node.exp.accept self - # Can only apply visibility modifier to def, macro or a macro call + # Can only apply visibility modifier to def, type, macro or a macro call case exp = node.exp + when ClassDef, ModuleDef, EnumDef, Alias, LibDef + if node.modifier.private? + return false + else + node.raise "can only use 'private' for types" + end + when Assign + if (target = exp.target).is_a?(Path) + if node.modifier.private? + return false + else + node.raise "can only use 'private' for constants" + end + end when Def return false when Macro @@ -812,7 +840,15 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end end - def lookup_type_def_name(path) + def lookup_type_def_name(node : ASTNode) + scope, name = lookup_type_def_name(node.name) + if current_type.is_a?(Program) + scope = program.check_private(node) || scope + end + {scope, name} + end + + def lookup_type_def_name(path : Path) if path.names.size == 1 && !path.global? scope = current_type name = path.names.first @@ -831,7 +867,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor unless target_type next_type = base_type path.names.each do |name| - next_type = base_type.lookup_path_item(name, lookup_in_namespace: false) + next_type = base_type.lookup_path_item(name, lookup_in_namespace: false, include_private: true, location: path.location) if next_type if next_type.is_a?(ASTNode) path.raise "execpted #{name} to be a type" @@ -857,4 +893,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.raise "can't use #{name} as a top-level type name: it's reserved for type arguments and free variables" end end + + def current_type_scope(node) + scope = current_type + if scope.is_a?(Program) && node.visibility.private? + scope = program.check_private(node) || scope + end + scope + end end diff --git a/src/compiler/crystal/semantic/type_lookup.cr b/src/compiler/crystal/semantic/type_lookup.cr index 294d5379ee05..5027c1a3e042 100644 --- a/src/compiler/crystal/semantic/type_lookup.cr +++ b/src/compiler/crystal/semantic/type_lookup.cr @@ -105,7 +105,7 @@ class Crystal::Type if node.names.size == 1 return free_var elsif free_var.is_a?(Type) - type = free_var.lookup_path(node.names[1..-1], lookup_in_namespace: false) + type = free_var.lookup_path(node.names[1..-1], lookup_in_namespace: false, location: node.location) end else type = @root.lookup_path(node) @@ -375,12 +375,7 @@ class Crystal::Type def raise_undefined_constant(node) check_cant_infer_generic_type_parameter(@root, node) - similar_name = @root.lookup_similar_path(node) - if similar_name - node.raise("undefined constant #{node} #{@root.program.colorize("(did you mean '#{similar_name}')").yellow.bold}") - else - node.raise("undefined constant #{node}") - end + node.raise_undefined_constant(@root) end def check_cant_infer_generic_type_parameter(scope, node) diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index fa22c89a60b0..42b4f5bbe1e7 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -667,6 +667,10 @@ module Crystal def initialize(@target, @value) end + def visibility=(visibility) + target.visibility = visibility + end + def accept_children(visitor) @target.accept visitor @value.accept visitor @@ -1177,6 +1181,7 @@ module Crystal property names : Array(String) property? global : Bool property name_size = 0 + property visibility = Visibility::Public def initialize(@names : Array, @global = false) end @@ -1220,6 +1225,7 @@ module Crystal property splat_index : Int32? property? abstract : Bool property? struct : Bool + property visibility = Visibility::Public def initialize(@name, body = nil, @superclass = nil, @type_vars = nil, @abstract = false, @struct = false, @name_column_number = 0, @splat_index = nil) @body = Expressions.from body @@ -1250,6 +1256,7 @@ module Crystal property splat_index : Int32? property name_column_number : Int32 property doc : String? + property visibility = Visibility::Public def initialize(@name, body = nil, @type_vars = nil, @name_column_number = 0, @splat_index = nil) @body = Expressions.from body @@ -1633,6 +1640,7 @@ module Crystal property name : String property body : ASTNode property name_column_number : Int32 + property visibility = Visibility::Public def initialize(@name, body = nil, @name_column_number = 0) @body = Expressions.from body @@ -1719,6 +1727,7 @@ module Crystal property members : Array(ASTNode) property base_type : ASTNode? property doc : String? + property visibility = Visibility::Public def initialize(@name, @members = [] of ASTNode, @base_type = nil) end @@ -1758,6 +1767,7 @@ module Crystal property name : String property value : ASTNode property doc : String? + property visibility = Visibility::Public def initialize(@name : String, @value : ASTNode) end diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index 325f37d8ffcd..32baa6043fd7 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -89,6 +89,7 @@ class Crystal::Doc::Generator end def must_include?(type : Crystal::Type) + return false if type.private? return false if nodoc?(type) return true if crystal_builtin?(type) diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index d31928bf200f..a9d49d4b5b04 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -521,6 +521,13 @@ module Crystal false end + def private? + false + end + + def private=(set_private) + end + def inspect(io) to_s(io) end @@ -542,6 +549,7 @@ module Crystal getter name : String getter locations : Array(Location)? property doc : String? + property? private : Bool = false def initialize(program, @namespace, @name) super(program)