mirrors 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +2 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +23 -0
  6. data/LICENSE.txt +59 -0
  7. data/README.md +1 -0
  8. data/asdfasdf.rb +53 -0
  9. data/bin/bundler +17 -0
  10. data/bin/byebug +17 -0
  11. data/bin/testunit +8 -0
  12. data/circle.yml +6 -0
  13. data/dev.yml +6 -0
  14. data/lib/mirrors.rb +150 -0
  15. data/lib/mirrors/class_mirror.rb +197 -0
  16. data/lib/mirrors/class_mixin.rb +11 -0
  17. data/lib/mirrors/field_mirror.rb +24 -0
  18. data/lib/mirrors/field_mirror/class_variable_mirror.rb +23 -0
  19. data/lib/mirrors/field_mirror/constant_mirror.rb +34 -0
  20. data/lib/mirrors/field_mirror/instance_variable_mirror.rb +23 -0
  21. data/lib/mirrors/hook.rb +33 -0
  22. data/lib/mirrors/index/indexer.rb +6 -0
  23. data/lib/mirrors/index/marker.rb +40 -0
  24. data/lib/mirrors/invoke.rb +29 -0
  25. data/lib/mirrors/method_mirror.rb +206 -0
  26. data/lib/mirrors/mirror.rb +37 -0
  27. data/lib/mirrors/object_mirror.rb +25 -0
  28. data/lib/mirrors/package_inference.rb +164 -0
  29. data/lib/mirrors/package_inference/class_to_file_resolver.rb +66 -0
  30. data/lib/mirrors/package_mirror.rb +33 -0
  31. data/lib/mirrors/visitors/disasm_visitor.rb +11 -0
  32. data/lib/mirrors/visitors/iseq_visitor.rb +84 -0
  33. data/lib/mirrors/visitors/references_visitor.rb +58 -0
  34. data/lib/mirrors/visitors/yasmdata.rb +212 -0
  35. data/lol.rb +35 -0
  36. data/mirrors.gemspec +19 -0
  37. data/test/fixtures/class.rb +29 -0
  38. data/test/fixtures/field.rb +9 -0
  39. data/test/fixtures/method.rb +15 -0
  40. data/test/fixtures/object.rb +5 -0
  41. data/test/fixtures/reflect.rb +14 -0
  42. data/test/mirrors/class_mirror_test.rb +87 -0
  43. data/test/mirrors/field_mirror_test.rb +125 -0
  44. data/test/mirrors/iseq_visitor_test.rb +56 -0
  45. data/test/mirrors/marker_test.rb +48 -0
  46. data/test/mirrors/method_mirror_test.rb +62 -0
  47. data/test/mirrors/object_mirror_test.rb +16 -0
  48. data/test/mirrors/package_inference_test.rb +31 -0
  49. data/test/mirrors/references_visitor_test.rb +30 -0
  50. data/test/mirrors_test.rb +38 -0
  51. data/test/test_helper.rb +12 -0
  52. metadata +137 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0d3d02baa921913fcc9ac830bba816d1a9f47339
4
+ data.tar.gz: 71253ea57e8ae471532753da621496811112da9b
5
+ SHA512:
6
+ metadata.gz: 6922717b62cd336253f51223ff28b3c7be93471c15f7117d92e6b61796a562c613c027ac0185275aa6947c7631e24e7b586113850652ef80f13ff6420db86fa2
7
+ data.tar.gz: 4f3ffb84b4cada62a5f67b47be3484dbb832b62831c886a73554d12c0f6f9641e69466e51cb5abc0393b62ab16dd2e3a4ace12d14a855ebb63b836c87dcbe764
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .rubocop-http*
4
+ .byebug_history
5
+ .vscode/
@@ -0,0 +1,2 @@
1
+ inherit_from:
2
+ - http://shopify.github.io/ruby-style-guide/rubocop.yml
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,23 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mirrors (0.0.1)
5
+ method_source (~> 0.8)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (9.0.6)
11
+ method_source (0.8.2)
12
+ minitest (5.9.1)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ byebug (~> 9.0.6)
19
+ minitest (~> 5.0)
20
+ mirrors!
21
+
22
+ BUNDLED WITH
23
+ 1.13.6
@@ -0,0 +1,59 @@
1
+ Copyright (c) 2016 Burke Libbey, Shopify
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. The names of the contributors may not be used
15
+ to endorse or promote products derived from this software without
16
+ specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+
30
+ --- most code derived from github.com/timfel/rubymirrors ---
31
+
32
+
33
+ Copyright (c) Tim Felgentreff and individual contributors.
34
+ All rights reserved.
35
+
36
+ Redistribution and use in source and binary forms, with or without modification,
37
+ are permitted provided that the following conditions are met:
38
+
39
+ 1. Redistributions of source code must retain the above copyright notice,
40
+ this list of conditions and the following disclaimer.
41
+
42
+ 2. Redistributions in binary form must reproduce the above copyright
43
+ notice, this list of conditions and the following disclaimer in the
44
+ documentation and/or other materials provided with the distribution.
45
+
46
+ 3. The names of the contributors may not be used
47
+ to endorse or promote products derived from this software without
48
+ specific prior written permission.
49
+
50
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
51
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
52
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
54
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
55
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
56
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
57
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
58
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
59
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ Use `bin/frontend` and `bin/backend`.
@@ -0,0 +1,53 @@
1
+ class Resolver
2
+ def resolve(klass)
3
+ @files ||= {}
4
+
5
+ klass.instance_methods(false).each do |name|
6
+ meth = klass.instance_method(name)
7
+
8
+ file = begin
9
+ meth.source_location[0]
10
+ rescue # ???
11
+ next
12
+ end
13
+
14
+ contents = (@files[file] ||= File.open(file, 'r') { |f| f.readpartial(4096) })
15
+ n = klass.name.sub(/.*::/, '') # last component of module name
16
+ return file if contents =~ /^\s+(class|module) ([\S]+::)?#{Regexp.quote(n)}\s/
17
+ end
18
+ nil
19
+ end
20
+ end
21
+
22
+ def infer_class_file_from_methods(klass)
23
+ if m = Resolver.new.resolve(klass)
24
+ return m
25
+ end
26
+
27
+ methods = klass
28
+ .instance_methods(false)
29
+ .map { |n| klass.instance_method(n) }
30
+
31
+ defined_directly_on_class = methods
32
+ .select do |meth|
33
+ # aliased methods can show up with instance_methods(false)
34
+ # but their source_location and owner point to the module they came from.
35
+ meth.owner == klass &&
36
+ meth.source =~ /\A\s+def #{Regexp.quote(meth.name)}/
37
+ # as a mostly-useful heuristic, we just eliminate everything that was
38
+ # defined using a template eval or define_method.
39
+ end
40
+
41
+ files = Hash.new(0)
42
+
43
+ defined_directly_on_class.each do |meth|
44
+ begin
45
+ files[meth.source_location[0]] += 1
46
+ rescue # which class?
47
+ raise
48
+ end
49
+ end
50
+
51
+ file = files.max_by { |k, v| v }
52
+ file ? file[0] : nil
53
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'bundler' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("bundler", "bundler")
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'byebug' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("byebug", "byebug")
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ if [ $# -eq 0 ]; then
4
+ ruby -I"test" -e 'Dir.glob("./test/**/*_test.rb").each { |f| require f }' -- "$@"
5
+ else
6
+ path=$1
7
+ ruby -I"test" -e "require '${path#test/}'" -- "$@"
8
+ fi
@@ -0,0 +1,6 @@
1
+ machine:
2
+ ruby:
3
+ version: ruby-2.3.1
4
+ test:
5
+ override:
6
+ - bundle exec bin/testunit
data/dev.yml ADDED
@@ -0,0 +1,6 @@
1
+ up:
2
+ - ruby: 2.3.1
3
+ - bundler
4
+ commands:
5
+ test: bundle exec bin/testunit
6
+ console: bundle exec irb -r mirrors
@@ -0,0 +1,150 @@
1
+ require 'logger'
2
+ require 'mirrors/mirror'
3
+ require 'mirrors/object_mirror'
4
+ require 'mirrors/class_mirror'
5
+ require 'mirrors/field_mirror'
6
+ require 'mirrors/method_mirror'
7
+ require 'mirrors/package_mirror'
8
+ require 'mirrors/package_inference'
9
+ require 'mirrors/class_mixin'
10
+ require 'mirrors/index/indexer'
11
+
12
+ module Mirrors
13
+ extend self
14
+
15
+ @class_mirrors = {}
16
+ @constant_mirrors = {}
17
+ @watches = {}
18
+ @logger = Logger.new(STDOUT)
19
+
20
+ def packages
21
+ packages = {}
22
+ # Object is the top-level.
23
+ Object.constants.each do |const|
24
+ pkg = PackageInference.infer_from_toplevel(const)
25
+ packages[pkg] = true
26
+ end
27
+ toplevel_packages = packages.keys.map { |pkg| pkg.sub(/:.*/, '') }.sort
28
+ package_mirrors(toplevel_packages)
29
+ end
30
+
31
+ # This method can be used to query the system for known modules. It
32
+ # is not guaranteed that all possible modules are returned.
33
+ #
34
+ # @return [Array<ClassMirror>] a list of class mirrors
35
+ def modules
36
+ instances_of(Module).sort! { |a, b| a.name <=> b.name }
37
+ end
38
+
39
+ # This method can be used to query the system for known classes. It
40
+ # is not guaranteed that all possible classes are returned.
41
+ #
42
+ # @return [Array<ClassMirror>] a list of class mirrors
43
+ def classes
44
+ instances_of(Class).sort! { |a, b| a.name <=> b.name }
45
+ end
46
+
47
+ # Query the system for objects that are direct instances of the
48
+ # given class.
49
+ # @param [Class]
50
+ # @return [Array<ObjectMirror>] a list of appropriate mirrors for the requested objects
51
+ def instances_of(klass)
52
+ mirrors(ObjectSpace.each_object(klass).select { |obj| obj.class == klass })
53
+ end
54
+
55
+ # Ask the system to find the object with the given object id
56
+ # @param [Numeric] object id
57
+ # @return [ObjectMirror, NilClass] the object mirror or nil
58
+ def object_by_id(id)
59
+ obj = ObjectSpace._id2ref(id)
60
+ obj ? reflect(obj) : nil
61
+ end
62
+
63
+ # Query the system for implementors of a particular message
64
+ # @param [String] the message name
65
+ # @return [Array<MethodMirror>] the implementing methods
66
+ def implementations_of(str)
67
+ methods = ObjectSpace.each_object(Module).collect do |m|
68
+ ims = m.instance_methods(false).collect { |s| m.instance_method(s) }
69
+ cms = m.methods(false).collect { |s| m.method(s) }
70
+ ims + cms
71
+ end.flatten
72
+
73
+ mirrors(methods.select { |m| m.name.to_s == str.to_s })
74
+ end
75
+
76
+ def references_to(str)
77
+ filtered = {}
78
+ Mirrors.classes.each do |klass|
79
+ klass.methods.each do |m|
80
+ refs = m.references.select { |marker| marker.message.match(str) }
81
+ filtered[m] = refs unless refs.empty?
82
+ end
83
+ end
84
+ filtered
85
+ end
86
+
87
+ # Create a mirror for a given object in the system under
88
+ # observation. This is *the* factory method for all mirror
89
+ # instances, interning and cache invalidation will be added here.
90
+ #
91
+ # @param [Object]
92
+ # @return [Mirror]
93
+ def reflect(obj)
94
+ klass = basic_class(obj)
95
+ mirror =
96
+ if klass == FieldMirror::Field || klass == Symbol
97
+ case obj.name.to_s
98
+ when /^@@/
99
+ intern_field_mirror(ClassVariableMirror.new(obj))
100
+ when /^@/
101
+ # instance variables not interned as they are not guaranteed to be
102
+ # present in all instances
103
+ InstanceVariableMirror.new(obj)
104
+ else
105
+ intern_field_mirror(ConstantMirror.new(obj))
106
+ end
107
+ elsif klass == Method || klass == UnboundMethod
108
+ intern_method_mirror(MethodMirror.new(obj))
109
+ elsif klass == Class || klass == Module
110
+ intern_class_mirror(ClassMirror.new(obj))
111
+ else
112
+ # TODO: revisit if ObjectMirror delivers value
113
+ ObjectMirror.new(obj)
114
+ end
115
+ raise "badness" unless mirror.is_a?(Mirror)
116
+ mirror
117
+ end
118
+
119
+ private
120
+
121
+ # find the class of obj
122
+ def basic_class(obj)
123
+ Kernel.instance_method(:class).bind(obj).call
124
+ end
125
+
126
+ # find the class name of obj
127
+ def basic_class_name(klass)
128
+ Class.instance_method(:name).bind(klass).call
129
+ end
130
+
131
+ def intern_class_mirror(mirror)
132
+ interned = @class_mirrors[mirror.name] ||= mirror
133
+ end
134
+
135
+ def intern_method_mirror(mirror)
136
+ mirror.defining_class.intern_method_mirror(mirror)
137
+ end
138
+
139
+ def intern_field_mirror(mirror)
140
+ mirror.defining_class.intern_field_mirror(mirror)
141
+ end
142
+
143
+ def mirrors(list)
144
+ list.map { |e| reflect(e) }
145
+ end
146
+
147
+ def package_mirrors(list)
148
+ list.map { |e| PackageMirror.reflect(e) }
149
+ end
150
+ end
@@ -0,0 +1,197 @@
1
+ module Mirrors
2
+ # A specific mirror for a class, that includes all the capabilites
3
+ # and information we can gather about classes.
4
+ class ClassMirror < ObjectMirror
5
+ def initialize(obj)
6
+ super(obj)
7
+ @field_mirrors = {}
8
+ @method_mirrors = {}
9
+ end
10
+
11
+ def is_class
12
+ @subject.is_a?(Class)
13
+ end
14
+
15
+ def package
16
+ # TODO(burke)
17
+ end
18
+
19
+ def fields
20
+ [constants, class_variables, class_instance_variables, instance_variables].flatten
21
+ end
22
+
23
+ # The known class variables.
24
+ # @see #instance_variables
25
+ # @return [Array<FieldMirror>]
26
+ def class_variables
27
+ field_mirrors(@subject.class_variables)
28
+ end
29
+
30
+ # The known class variables.
31
+ # @see #instance_variables
32
+ # @return [Array<FieldMirror>]
33
+ def class_instance_variables
34
+ field_mirrors(@subject.instance_variables)
35
+ end
36
+
37
+ # The source files this class is defined and/or extended in.
38
+ #
39
+ # @return [Array<String,File>]
40
+ def source_files
41
+ locations = @subject.instance_methods(false).collect do |name|
42
+ method = @subject.instance_method(name)
43
+ sl = method.source_location
44
+ sl.first if sl
45
+ end
46
+ locations.compact.uniq
47
+ end
48
+
49
+ # The singleton class of this class
50
+ #
51
+ # @return [ClassMirror]
52
+ def singleton_class
53
+ Mirrors.reflect(@subject.singleton_class)
54
+ end
55
+
56
+ # Predicate to determine whether the subject is a singleton class
57
+ #
58
+ # @return [true,false]
59
+ def singleton_class?
60
+ name.match(/^\#<Class:.*>$/)
61
+ end
62
+
63
+ # The mixins included in the ancestors of this class.
64
+ #
65
+ # @return [Array<ClassMirror>]
66
+ def mixins
67
+ mirrors(@subject.ancestors.reject { |m| m.is_a?(Class) })
68
+ end
69
+
70
+ # The direct superclass
71
+ #
72
+ # @return [ClassMirror]
73
+ def superclass
74
+ Mirrors.reflect(@subject.superclass)
75
+ end
76
+
77
+ # The known subclasses
78
+ #
79
+ # @return [Array<ClassMirror>]
80
+ def subclasses
81
+ mirrors(ObjectSpace.each_object(Class).select { |a| a.superclass == @subject })
82
+ end
83
+
84
+ # The list of ancestors
85
+ #
86
+ # @return [Array<ClassMirror>]
87
+ def ancestors
88
+ mirrors(@subject.ancestors)
89
+ end
90
+
91
+ # The constants defined within this class. This includes nested
92
+ # classes and modules, but also all other kinds of constants.
93
+ #
94
+ # @return [Array<FieldMirror>]
95
+ def constants
96
+ field_mirrors(@subject.constants)
97
+ end
98
+
99
+ # Searches for the named constant in the mirrored namespace. May
100
+ # include a colon (::) separated constant path. This _may_ trigger
101
+ # an autoload!
102
+ #
103
+ # @return [ClassMirror, nil] the requested constant, or nil
104
+ def constant(str)
105
+ path = str.to_s.split("::")
106
+ c = path[0..-2].inject(@subject) { |klass, s| klass.const_get(s) }
107
+ field_mirror (c || @subject), path.last
108
+ rescue NameError => e
109
+ p e
110
+ nil
111
+ end
112
+
113
+ # The full nesting.
114
+ #
115
+ # @return [Array<ClassMirror>]
116
+ def nesting
117
+ ary = []
118
+ @subject.name.split('::').inject(Object) do |klass, str|
119
+ ary << klass.const_get(str)
120
+ ary.last
121
+ end
122
+ ary.reverse
123
+ rescue NameError
124
+ [@subject]
125
+ end
126
+
127
+ # The classes nested within the subject. Should _not_ trigger
128
+ # autloads!
129
+ #
130
+ # @return [Array<ClassMirror>]
131
+ def nested_classes
132
+ nc = @subject.constants.collect do |c|
133
+ # do not trigger autoloads
134
+ if @subject.const_defined?(c) && !@subject.autoload?(c)
135
+ @subject.const_get(c)
136
+ end
137
+ end
138
+ mirrors(nc.compact.select { |c| c.is_a?(Module) }.sort_by(&:name))
139
+ end
140
+
141
+ def nested_class_count
142
+ nested_classes.count
143
+ end
144
+
145
+ # The instance methods of this class. To get to the class methods,
146
+ # ask the #singleton_class for its methods.
147
+ #
148
+ # @return [Array<MethodMirror>]
149
+ def methods
150
+ pub_names = @subject.public_instance_methods(false)
151
+ prot_names = @subject.protected_instance_methods(false)
152
+ priv_names = @subject.private_instance_methods(false)
153
+
154
+ mirrors = []
155
+ pub_names.sort.each do |n|
156
+ mirrors << Mirrors.reflect(@subject.instance_method(n))
157
+ end
158
+ prot_names.sort.each do |n|
159
+ mirrors << Mirrors.reflect(@subject.instance_method(n))
160
+ end
161
+ priv_names.sort.each do |n|
162
+ mirrors << Mirrors.reflect(@subject.instance_method(n))
163
+ end
164
+ mirrors
165
+ end
166
+
167
+ # The instance method of this class or any of its superclasses
168
+ # that has the specified selector
169
+ #
170
+ # @return [MethodMirror, nil] the method or nil, if none was found
171
+ def method(name)
172
+ Mirrors.reflect @subject.instance_method(name)
173
+ end
174
+
175
+ # to work around overridden `name` methods
176
+ MODULE_INSPECT = Module.instance_method(:inspect)
177
+ def name
178
+ MODULE_INSPECT.bind(@subject).call
179
+ rescue
180
+ puts @subject.inspect
181
+ puts @subject.class
182
+ raise
183
+ end
184
+
185
+ def demodulized_name
186
+ name.split('::').last
187
+ end
188
+
189
+ def intern_method_mirror(mirror)
190
+ @method_mirrors[mirror.name] ||= mirror
191
+ end
192
+
193
+ def intern_field_mirror(mirror)
194
+ @field_mirrors[mirror.name] ||= mirror
195
+ end
196
+ end
197
+ end