houndstooth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +49 -0
- data/README.md +99 -0
- data/bin/houndstooth.rb +183 -0
- data/fuzz/cases/x.rb +8 -0
- data/fuzz/cases/y.rb +8 -0
- data/fuzz/cases/z.rb +22 -0
- data/fuzz/ruby.dict +64 -0
- data/fuzz/run +21 -0
- data/lib/houndstooth/environment/builder.rb +260 -0
- data/lib/houndstooth/environment/type_parser.rb +149 -0
- data/lib/houndstooth/environment/types/basic/type.rb +85 -0
- data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
- data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
- data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
- data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
- data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
- data/lib/houndstooth/environment/types/method/method.rb +79 -0
- data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
- data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
- data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
- data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
- data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
- data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
- data/lib/houndstooth/environment/types.rb +3 -0
- data/lib/houndstooth/environment.rb +74 -0
- data/lib/houndstooth/errors.rb +53 -0
- data/lib/houndstooth/instructions.rb +698 -0
- data/lib/houndstooth/interpreter/const_internal.rb +148 -0
- data/lib/houndstooth/interpreter/objects.rb +142 -0
- data/lib/houndstooth/interpreter/runtime.rb +309 -0
- data/lib/houndstooth/interpreter.rb +7 -0
- data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
- data/lib/houndstooth/semantic_node/definitions.rb +253 -0
- data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
- data/lib/houndstooth/semantic_node/keywords.rb +45 -0
- data/lib/houndstooth/semantic_node/literals.rb +226 -0
- data/lib/houndstooth/semantic_node/operators.rb +126 -0
- data/lib/houndstooth/semantic_node/parameters.rb +108 -0
- data/lib/houndstooth/semantic_node/send.rb +349 -0
- data/lib/houndstooth/semantic_node/super.rb +12 -0
- data/lib/houndstooth/semantic_node.rb +119 -0
- data/lib/houndstooth/stdlib.rb +6 -0
- data/lib/houndstooth/type_checker.rb +462 -0
- data/lib/houndstooth.rb +53 -0
- data/spec/ast_to_node_spec.rb +889 -0
- data/spec/environment_spec.rb +323 -0
- data/spec/instructions_spec.rb +291 -0
- data/spec/integration_spec.rb +785 -0
- data/spec/interpreter_spec.rb +170 -0
- data/spec/self_spec.rb +7 -0
- data/spec/spec_helper.rb +50 -0
- data/test/ruby_interpreter_test.rb +162 -0
- data/types/stdlib.htt +170 -0
- metadata +110 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
class Houndstooth::Environment
|
2
|
+
class DefinedType < Type
|
3
|
+
def initialize(path: nil, node: nil, superclass: nil, instance_methods: nil, eigen: :generate, type_parameters: nil)
|
4
|
+
@path = path.to_s
|
5
|
+
@node = node
|
6
|
+
@superclass = superclass
|
7
|
+
@instance_methods = instance_methods || []
|
8
|
+
@type_parameters = type_parameters || []
|
9
|
+
@type_instance_variables = {}
|
10
|
+
|
11
|
+
if eigen == :generate
|
12
|
+
@eigen = DefinedType.new(
|
13
|
+
path: "<Eigen:#{path}>",
|
14
|
+
superclass:
|
15
|
+
if superclass.is_a?(PendingDefinedType)
|
16
|
+
PendingDefinedType.new("<Eigen:#{superclass.path}>")
|
17
|
+
else
|
18
|
+
superclass&.eigen
|
19
|
+
end,
|
20
|
+
eigen: nil,
|
21
|
+
)
|
22
|
+
else
|
23
|
+
@eigen = eigen
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
attr_reader :path
|
29
|
+
|
30
|
+
# @return [String]
|
31
|
+
def name
|
32
|
+
path.split("::").last
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [SemanticNode]
|
36
|
+
attr_reader :node
|
37
|
+
|
38
|
+
# @return [Type, nil]
|
39
|
+
attr_accessor :superclass
|
40
|
+
|
41
|
+
# @return [Type]
|
42
|
+
attr_accessor :eigen
|
43
|
+
|
44
|
+
# @return [<Method>]
|
45
|
+
attr_reader :instance_methods
|
46
|
+
|
47
|
+
# @return [<String>]
|
48
|
+
attr_reader :type_parameters
|
49
|
+
|
50
|
+
# @return [{String => Type}]
|
51
|
+
attr_reader :type_instance_variables
|
52
|
+
|
53
|
+
def resolve_instance_method(method_name, env, instance: nil, top_level: true)
|
54
|
+
# Is it available on this type?
|
55
|
+
# If not, check the superclass
|
56
|
+
# If there's no superclass, then there is no method to be found, so return nil
|
57
|
+
instance_method = instance_methods.find { _1.name == method_name }
|
58
|
+
|
59
|
+
found = if instance_method
|
60
|
+
instance_method
|
61
|
+
else
|
62
|
+
superclass&.resolve_instance_method(method_name, env, instance: instance, top_level: false)
|
63
|
+
end
|
64
|
+
|
65
|
+
# If the upper chain returned a special constructor method, we need to convert this by
|
66
|
+
# grabbing our instance's `initialize` type
|
67
|
+
if top_level && found && found.is_a?(SpecialConstructorMethod)
|
68
|
+
initialize_sig = env.resolve_type(uneigen).resolve_instance_method(:initialize, env, instance: instance)
|
69
|
+
@new_sig ||= Method.new(
|
70
|
+
:new,
|
71
|
+
initialize_sig.signatures.map do |sig|
|
72
|
+
# Same parameters, but returns `instance`
|
73
|
+
new_sig = sig.clone
|
74
|
+
new_sig.return_type = InstanceType.new
|
75
|
+
new_sig
|
76
|
+
end,
|
77
|
+
const: initialize_sig.const,
|
78
|
+
)
|
79
|
+
@new_sig
|
80
|
+
else
|
81
|
+
found
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def resolve_instance_variable(name)
|
86
|
+
var_here = type_instance_variables[name]
|
87
|
+
return var_here if var_here
|
88
|
+
|
89
|
+
superclass&.resolve_instance_variable(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# A path to this type, but with one layer of "eigen-ness" removed from the final element.
|
93
|
+
# A bit cursed, but used for constant resolution.
|
94
|
+
# @return [String]
|
95
|
+
def uneigen
|
96
|
+
path_parts = path.split("::")
|
97
|
+
*rest, name = path_parts
|
98
|
+
|
99
|
+
raise "internal error: can't uneigen a non-eigen type" unless /^<Eigen:(.+)>$/ === name
|
100
|
+
uneigened_name = $1
|
101
|
+
|
102
|
+
[*rest, uneigened_name].join("::")
|
103
|
+
end
|
104
|
+
|
105
|
+
def resolve_all_pending_types(environment, context: nil)
|
106
|
+
@superclass = resolve_type_if_pending(superclass, self, environment)
|
107
|
+
@eigen = resolve_type_if_pending(eigen, self, environment)
|
108
|
+
|
109
|
+
instance_methods.map do |method|
|
110
|
+
method.resolve_all_pending_types(environment, context: self)
|
111
|
+
end
|
112
|
+
|
113
|
+
type_instance_variables.keys.each do |k|
|
114
|
+
type_instance_variables[k] = resolve_type_if_pending(type_instance_variables[k], self, environment)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def accepts?(other)
|
119
|
+
return false unless other.is_a?(DefinedType)
|
120
|
+
|
121
|
+
distance = 0
|
122
|
+
current = other
|
123
|
+
until current.nil?
|
124
|
+
return distance if current == self
|
125
|
+
|
126
|
+
current = current&.superclass
|
127
|
+
distance += 1
|
128
|
+
end
|
129
|
+
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
def rbs
|
134
|
+
path
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Houndstooth::Environment
|
2
|
+
class Method
|
3
|
+
# @return [String]
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
# @return [<MethodType>]
|
7
|
+
attr_reader :signatures
|
8
|
+
|
9
|
+
# :public, :protected or :private
|
10
|
+
# @return [Symbol]
|
11
|
+
attr_reader :visibility
|
12
|
+
|
13
|
+
# If a symbol, the kind of constness this method has:
|
14
|
+
# - :normal, defined as user-specified source, can be used anywhere
|
15
|
+
# - :internal, defined in Houndstooth, can be used anywhere
|
16
|
+
# - :required, defined as user-specified source, can only be used from a const context
|
17
|
+
# - :required_internal, defined in Houndstooth, can only be used from a const context
|
18
|
+
# If nil, this method is not const.
|
19
|
+
# @return [Symbol, nil]
|
20
|
+
attr_reader :const
|
21
|
+
|
22
|
+
def const?; !const.nil?; end
|
23
|
+
def const_internal?; const == :internal || const == :required_internal; end
|
24
|
+
def const_required?; const == :required || const == :required_internal; end
|
25
|
+
|
26
|
+
# The instruction block which implements this method.
|
27
|
+
# @return [InstructionBlock]
|
28
|
+
attr_accessor :instruction_block
|
29
|
+
|
30
|
+
def initialize(name, signatures = nil, visibility: :public, const: false)
|
31
|
+
@name = name
|
32
|
+
@signatures = signatures || []
|
33
|
+
@visibility = visibility
|
34
|
+
@const = const
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve_all_pending_types(environment, context:)
|
38
|
+
signatures.map do |sig|
|
39
|
+
sig.resolve_all_pending_types(environment, context: context)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def substitute_type_parameters(instance, call_type_args)
|
44
|
+
raise 'internal error: tried to substitute parameters on a Method; too high in the hierarchy for this to be sensible'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Given a set of arguments and their types, resolves and returns the best matching signature
|
48
|
+
# of this method.
|
49
|
+
#
|
50
|
+
# If multiple signatures match, the "best" is chosen according to the distance rules used
|
51
|
+
# by `Type#accepts?` - the type with the lowest distance over all arguments is returned.
|
52
|
+
# If no signatures match, returns nil.
|
53
|
+
#
|
54
|
+
# @param [TypeInstance] instance
|
55
|
+
# @param [<(Instructions::Argument, Type)>] arguments
|
56
|
+
# @param [<Type>] type_arguments
|
57
|
+
# @return [MethodType, nil]
|
58
|
+
def resolve_matching_signature(instance, arguments, type_arguments = nil)
|
59
|
+
type_arguments ||= []
|
60
|
+
|
61
|
+
sigs_with_scores = signatures
|
62
|
+
.map do |sig|
|
63
|
+
# Create {name => type} type argument mapping, or if the numbers mismatch
|
64
|
+
# return false, as this signature cannot match
|
65
|
+
next [nil, false] if type_arguments.length != sig.type_parameters.length
|
66
|
+
|
67
|
+
call_type_args = sig.type_parameters.zip(type_arguments).to_h
|
68
|
+
[sig, sig.substitute_type_parameters(instance, call_type_args).accepts_arguments?(arguments)]
|
69
|
+
end
|
70
|
+
.reject { |_, r| r == false }
|
71
|
+
|
72
|
+
if sigs_with_scores.any?
|
73
|
+
sigs_with_scores.min_by { |sig, score| score }[0]
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
class Houndstooth::Environment
|
2
|
+
class MethodType < Type
|
3
|
+
# @return [<PositionalParameter>]
|
4
|
+
attr_accessor :positional_parameters
|
5
|
+
|
6
|
+
# @return [<KeywordParameter>]
|
7
|
+
attr_accessor :keyword_parameters
|
8
|
+
|
9
|
+
# @return [PositionalParameter, nil]
|
10
|
+
attr_accessor :rest_positional_parameter
|
11
|
+
|
12
|
+
# @return [KeywordParameter, nil]
|
13
|
+
attr_accessor :rest_keyword_parameter
|
14
|
+
|
15
|
+
# @return [BlockParameter, nil]
|
16
|
+
attr_accessor :block_parameter
|
17
|
+
|
18
|
+
# @return [Type]
|
19
|
+
attr_accessor :return_type
|
20
|
+
|
21
|
+
# @return [<String>]
|
22
|
+
attr_accessor :type_parameters
|
23
|
+
|
24
|
+
def initialize(positional: [], keyword: [], rest_positional: nil, rest_keyword: nil, block: nil, return_type: nil, type_parameters: nil)
|
25
|
+
super()
|
26
|
+
|
27
|
+
@positional_parameters = positional
|
28
|
+
@keyword_parameters = keyword
|
29
|
+
@rest_positional_parameter = rest_positional
|
30
|
+
@rest_keyword_parameter = rest_keyword
|
31
|
+
@block_parameter = block
|
32
|
+
@return_type = return_type || VoidType.new
|
33
|
+
@type_parameters = type_parameters || []
|
34
|
+
end
|
35
|
+
|
36
|
+
def resolve_all_pending_types(environment, context:)
|
37
|
+
@return_type = resolve_type_if_pending(return_type, context, environment)
|
38
|
+
|
39
|
+
positional_parameters.map do |param|
|
40
|
+
param.resolve_all_pending_types(environment, context: context)
|
41
|
+
end
|
42
|
+
|
43
|
+
keyword_parameters.map do |param|
|
44
|
+
param.resolve_all_pending_types(environment, context: context)
|
45
|
+
end
|
46
|
+
|
47
|
+
rest_positional_parameter&.resolve_all_pending_types(environment, context: context)
|
48
|
+
rest_keyword_parameter&.resolve_all_pending_types(environment, context: context)
|
49
|
+
block_parameter&.resolve_all_pending_types(environment, context: context)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Determines whether this method can be called with the given arguments and their types.
|
53
|
+
# Follows the same return-value rules as `accepts?`.
|
54
|
+
#
|
55
|
+
# @param [<(Instructions::Argument, Type)>] arguments
|
56
|
+
# @return [Integer, Boolean]
|
57
|
+
def accepts_arguments?(arguments)
|
58
|
+
distance_total = 0
|
59
|
+
args_index = 0
|
60
|
+
|
61
|
+
# Check the positional parameters first
|
62
|
+
positional_parameters.each do |param|
|
63
|
+
# Is there also a positional argument in this index slot?
|
64
|
+
this_arg, this_type = arguments[args_index]
|
65
|
+
if this_arg.is_a?(Houndstooth::Instructions::PositionalArgument)
|
66
|
+
# Yes, so this argument was definitely passed to this parameter
|
67
|
+
# Are the types compatible?
|
68
|
+
dist = param.type.accepts?(this_type)
|
69
|
+
if dist
|
70
|
+
# Yep! All is well. Add to total distance
|
71
|
+
distance_total += dist
|
72
|
+
args_index += 1
|
73
|
+
else
|
74
|
+
# Nope, this isn't valid. Bail
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# No positional argument - but that's OK if this parameter is optional
|
79
|
+
if !param.optional?
|
80
|
+
# Missing argument not allowed
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Are there any positional arguments left over?
|
87
|
+
while arguments[args_index] && arguments[args_index][0].is_a?(Houndstooth::Instructions::PositionalArgument)
|
88
|
+
this_arg, this_type = arguments[args_index]
|
89
|
+
|
90
|
+
# Is there a rest-parameter to take these?
|
91
|
+
if !rest_positional_parameter.nil?
|
92
|
+
# Yep, but does this argument match the type of the rest positional?
|
93
|
+
dist = param.type.accepts?(this_type)
|
94
|
+
if dist
|
95
|
+
# Correct - this is passed into the splat!
|
96
|
+
distance_total += dist
|
97
|
+
args_index += 1
|
98
|
+
else
|
99
|
+
# Not the right type for the splat, invalid
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
else
|
103
|
+
# No, error - too many arguments
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# TODO: keyword arguments
|
109
|
+
raise "keyword argument checking not implemeneted" \
|
110
|
+
if arguments.find { |x, _| x.is_a?(Houndstooth::Instructions::KeywordArgument) }
|
111
|
+
|
112
|
+
distance_total
|
113
|
+
end
|
114
|
+
|
115
|
+
def substitute_type_parameters(instance, call_type_args)
|
116
|
+
clone.tap do |t|
|
117
|
+
t.positional_parameters = t.positional_parameters.map { |p| p.substitute_type_parameters(instance, call_type_args) }
|
118
|
+
t.keyword_parameters = t.keyword_parameters.map { |p| p.substitute_type_parameters(instance, call_type_args) }
|
119
|
+
|
120
|
+
t.rest_positional_parameter = t.rest_positional_parameter&.substitute_type_parameters(instance, call_type_args)
|
121
|
+
t.rest_keyword_parameter = t.rest_keyword_parameter&.substitute_type_parameters(instance, call_type_args)
|
122
|
+
t.block_parameter = t.block_parameter&.substitute_type_parameters(instance, call_type_args)
|
123
|
+
|
124
|
+
t.return_type = t.return_type.substitute_type_parameters(instance, call_type_args)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# TODO: implement accepts?
|
129
|
+
|
130
|
+
def rbs
|
131
|
+
params =
|
132
|
+
[positional_parameters.map(&:rbs), keyword_parameters.map(&:rbs)].flatten.join(", ")
|
133
|
+
|
134
|
+
if type_parameters.any?
|
135
|
+
type_params = "[#{type_parameters.join(', ')}] "
|
136
|
+
else
|
137
|
+
type_params = ''
|
138
|
+
end
|
139
|
+
|
140
|
+
"#{type_params}(#{params}) #{block_parameter ? "#{block_parameter.rbs} " : ''}-> #{return_type.rbs}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Houndstooth::Environment
|
2
|
+
class Parameter < Type
|
3
|
+
# Note: Parameters aren't *really* a type, but we need `resolve_type_if_pending`
|
4
|
+
|
5
|
+
# @return [Name]
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# @return [Type]
|
9
|
+
attr_accessor :type
|
10
|
+
|
11
|
+
# @return [Boolean]
|
12
|
+
attr_reader :optional
|
13
|
+
alias optional? optional
|
14
|
+
|
15
|
+
def initialize(name, type, optional: false)
|
16
|
+
@name = name
|
17
|
+
@type = type
|
18
|
+
@optional = optional
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolve_all_pending_types(environment, context:)
|
22
|
+
@type = resolve_type_if_pending(type, context, environment)
|
23
|
+
end
|
24
|
+
|
25
|
+
def substitute_type_parameters(instance, call_type_args)
|
26
|
+
clone.tap do |t|
|
27
|
+
t.type = t.type.substitute_type_parameters(instance, call_type_args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class PositionalParameter < Parameter
|
33
|
+
def rbs
|
34
|
+
if name
|
35
|
+
"#{optional? ? '?' : ''}#{type.rbs} #{name}"
|
36
|
+
else
|
37
|
+
"#{optional? ? '?' : ''}#{type.rbs}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class KeywordParameter < Parameter
|
43
|
+
def rbs
|
44
|
+
"#{optional? ? '?' : ''}#{name}: #{type.rbs}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class BlockParameter < Parameter
|
49
|
+
def rbs
|
50
|
+
"#{optional? ? '?' : ''}{ #{type.rbs} }"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Houndstooth::Environment
|
2
|
+
# A special type which can be used in place of a method, typically only `new`. Specifies
|
3
|
+
# that the resolved instance method should actually be taken from an uneigened `initialize`.
|
4
|
+
class SpecialConstructorMethod < Type
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :name
|
10
|
+
|
11
|
+
def rbs
|
12
|
+
"<special constructor '#{name}'>"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Houndstooth::Environment
|
2
|
+
# A type which will be replaced by a type argument later.
|
3
|
+
class TypeParameterPlaceholder < Type
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def accepts?(other)
|
11
|
+
if other.is_a?(TypeParameterPlaceholder) && name == other.name
|
12
|
+
1
|
13
|
+
else
|
14
|
+
false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def rbs
|
19
|
+
name
|
20
|
+
end
|
21
|
+
|
22
|
+
def substitute_type_parameters(instance, call_type_args)
|
23
|
+
# Call type arguments take priority, check those first
|
24
|
+
return call_type_args[name] if call_type_args[name]
|
25
|
+
|
26
|
+
# Get index of type parameter
|
27
|
+
index = instance.type.type_parameters.index { |tp| tp == name } or return self
|
28
|
+
|
29
|
+
# Replace with type argument, which should be an instance
|
30
|
+
instance.type_arguments[index] or self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Yikes!
|
34
|
+
# It doesn't ever make sense to instantiate a type parameter, and trying to do so was
|
35
|
+
# causing problems when passing type arguments around functions, so just don't allow it
|
36
|
+
def instantiate(...) = self
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Houndstooth::Environment
|
2
|
+
def initialize
|
3
|
+
@types = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def add_type(type)
|
7
|
+
# Add the type and its entire eigen chain
|
8
|
+
@types[type.path] = type
|
9
|
+
add_type(type.eigen) if type.respond_to?(:eigen) && type.eigen
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [{String, DefinedType}]
|
13
|
+
attr_reader :types
|
14
|
+
|
15
|
+
def resolve_all_pending_types
|
16
|
+
types.each do |_, type|
|
17
|
+
type.resolve_all_pending_types(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Resolve a type by path; either an absolute path from the root namespace, or optionally as a
|
22
|
+
# relative path from the context of it being used within the given type.
|
23
|
+
# If the type does not exist, returns nil.
|
24
|
+
#
|
25
|
+
# @param [String] path The path to resolve. If `type_context` is nil, this is interpreted as an
|
26
|
+
# absolute path regardless of whether it is prefixed with `::`. If `type_context` is given,
|
27
|
+
# this is interpreted as a relative path without a `::` prefix, or an absolute path with one.
|
28
|
+
# @param [DefinedType] type_context Optional: The context to search from.
|
29
|
+
#
|
30
|
+
# @return [DefinedType, nil]
|
31
|
+
def resolve_type(path, type_context: nil)
|
32
|
+
if path.start_with?('::') || type_context.nil?
|
33
|
+
# Our `#types` field is indexed by absolute path, let's just look at that!
|
34
|
+
# Prune the :: if present
|
35
|
+
path = path[2..] if path.start_with?('::')
|
36
|
+
return types[path]
|
37
|
+
end
|
38
|
+
|
39
|
+
# This is a relative path - split into parts
|
40
|
+
path_parts = path.split('::')
|
41
|
+
return nil if path_parts.empty?
|
42
|
+
next_part, *rest_parts = *path_parts
|
43
|
+
|
44
|
+
# Does the current type context contain the next part of the path?
|
45
|
+
maybe_inner_type = types[type_context.path + '::' + next_part]
|
46
|
+
if maybe_inner_type
|
47
|
+
# Yes - either return if there's no more parts, or advance into that type and continue
|
48
|
+
# the search
|
49
|
+
if rest_parts.empty?
|
50
|
+
maybe_inner_type
|
51
|
+
else
|
52
|
+
resolve_type(rest_parts.join('::'), type_context: maybe_inner_type)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
# No - check the current type context's parent
|
56
|
+
# (Or, if there's no parent, we'll try searching for it as absolute as a last-ditch
|
57
|
+
# attempt)
|
58
|
+
if type_context.path.include?('::')
|
59
|
+
resolve_type(path, type_context: types[type_context.path.split('::')[...-1].join('::')])
|
60
|
+
else
|
61
|
+
resolve_type(path, type_context: nil)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect
|
67
|
+
"#<Houndstooth::Environment (#{types.length} types)>"
|
68
|
+
end
|
69
|
+
alias to_s inspect
|
70
|
+
end
|
71
|
+
|
72
|
+
require_relative 'environment/types'
|
73
|
+
require_relative 'environment/type_parser'
|
74
|
+
require_relative 'environment/builder'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Houndstooth
|
2
|
+
module Errors
|
3
|
+
class Error
|
4
|
+
def initialize(message, tagged_ranges)
|
5
|
+
@message = message
|
6
|
+
@tagged_ranges = tagged_ranges
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :message
|
11
|
+
|
12
|
+
# @return [(Parser::Source::Range, String)]
|
13
|
+
attr_reader :tagged_ranges
|
14
|
+
|
15
|
+
def format
|
16
|
+
# TODO: merge nearby errors
|
17
|
+
|
18
|
+
(["Error: #{message}"] \
|
19
|
+
+ tagged_ranges.flat_map do |range, hint|
|
20
|
+
# TODO: won't work if the error spans multiple lines
|
21
|
+
line_range = range.source_buffer.line_range(range.line)
|
22
|
+
begin_pos_on_line = range.begin_pos - line_range.begin_pos
|
23
|
+
length = range.end_pos - range.begin_pos
|
24
|
+
|
25
|
+
[
|
26
|
+
"",
|
27
|
+
" #{range.source_buffer.name}",
|
28
|
+
" #{range.line} | #{range.source_line}",
|
29
|
+
" #{' ' * range.line.to_s.length} #{' ' * begin_pos_on_line}#{'^' * length} #{hint}",
|
30
|
+
]
|
31
|
+
end).join("\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
def push
|
35
|
+
Errors.push(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@errors = []
|
40
|
+
|
41
|
+
def self.reset
|
42
|
+
@errors = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.push(error)
|
46
|
+
@errors << error
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.errors
|
50
|
+
@errors
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|