在 Finder 中浏览 "Senders" 也 returns 代码不包含我在 Pharo 中搜索的消息?

Browsing "Senders" in Finder also returns code that doesn't contain the message I'm searching for in Pharo?

打开一张全新的 Pharo 5 图片,打开 Finder 并搜索 + 选择器。然后选择 + 并单击发件人。得到了大约 5000 个结果,其中大部分看起来都不错,但仔细检查后,有些结果似乎缺少对我的消息发送的任何引用。

我可以补充的不多,其中大约 1700 个似乎是错误的,并且没有包含 + 什么的参考。你认为问题是什么?这是一个例子:

更新

搜索通过文本源代码和字节码进行;所以它向你展示了方法,因为它在字节码中找到了 #+,但是浏览器太有限,无法同时显示文本和字节,所以它只显示文本(即使匹配是在字节码上完成的)

至于字节码本身,Pharo 会在您每次保存时(重新)编译一个方法;例如,当您保存以下方法时

Something>>loop
    1 to: 10 do: [ :each | ]

系统会编译它,当你检查方法和查看字节码时,你会看到这个

我相信你也可以手写字节码。


原回答

因为对于某些特殊的 selector,它还会查看字节码,您可以在检查方法本身时看到它。

通过查看 Pharo 本身的代码,可以在不到一分钟的时间内很容易地发现这一点(一旦您对 Pharo 更加熟悉):

  • #+ senders 为您提供 select 或
  • 的发件人列表
  • 所以您查看 senders 的实现并浏览可用的调用链(您可以 select select 或者使用鼠标或双击和 ctrl+n 向您展示发件人浏览器)
    • sendersallSendersOf:thoroughWhichSelectorsReferTo:
  • 你看到了

.

thoroughWhichSelectorsReferTo: literal
    "Answer a set of selectors whose methods access the argument as a 
    literal. Dives into the compact literal notation, making it slow but 
    thorough "
    | selectors special byte |
    "for speed we check the special selectors here once per class"
    special := Smalltalk
        hasSpecialSelector: literal
        ifTrueSetByte: [ :value | byte := value ].
    selectors := OrderedCollection new.
    self selectorsAndMethodsDo: [ :sel :method |
            ((method refersToLiteral: literal) or: [special and: [method scanFor: byte]]) ifTrue: [selectors add: sel]].
    ^ selectors

稍微澄清一下@Peter 的回答:

迭代

  1 to: 10 do: [:each | dictionary at: each put: each]

每次迭代块时将+ 1发送到块变量each

我们没有在源代码中看到 + 1,但它确实发生在 幕后 。之所以发现这样一个隐藏的发送,是因为 Smalltalk 不分析源代码,而是分析 CompiledMethod 并通过查看文字框架来做到这一点,正如 Peter 所说,还查看字节码。

例如,通过编译和检查方法

m
  1 to: 10 do: [:i | i foo]

我们可以看到中间表示 Ir 选项卡显示为:

 1. label: 1
 2. pushLiteral: 1
 3. popIntoTemp: #i
 4. goto: 2

 5. label: 2
 6. pushTemp: #i
 7. pushLiteral: 10
 8. send: #'<='
 9. if: false goto: 4 else: 3

10. label: 3
11. pushTemp: #i
12. send: #foo
13. popTop
14. pushTemp: #i
15. pushLiteral: 1
16. send: #+                       "Here we sum 1 to i"
17. popIntoTemp: #i
18. goto: 2

19. label: 4
20. returnReceiver

第 15 和 16 行表示实际发生的 + 1。请注意,符号 #+ 不在文字框架中(检查器的原始选项卡)。它也不在 AST 中,因为源代码中没有 +


为什么会这样?

有一些特殊消息,#to:do: 就是其中之一,当您的代码发送它们时,它们会 内联 。内联一个方法意味着在发送者中包含它的字节码而不是实际发送它们。

例如,如果您的代码显示为

self foo.
1 to: 10 do: [:i | i foo].
self bar

您的方法有 4 个发送:footo:do:foo(再次)和 bar。但是,编译器将只生成 3 个发送 foofoobar,并将包含 to:do: 的字节码。

并且鉴于 to:do: 需要将 1 与块参数 i 相加,实际发送的 send + 被检测为出现在您的方法中(因为它确实如此)。

但是,如果将方法重写为

m
  | block |
  block := [:i | i foo].
  self foo.
  1 to: 10 do: block
  self bar

编译器将拒绝内联 to:do: 并将其作为常规消息发送。因此,您的方法将不再是 + 的发送者。


如果to:do: 没有发送,为什么我的方法是它的发送者?

这是一个比较微妙的问题。正如我们所见,当 to:do: 被内联时,它不会被发送。那么我的方法怎么可能被识别为 to:do: 的发送者?

嗯,原因是编译器无论如何都会将符号 #to:do: 添加到您的方法的文字框架中。它这样做只是为了让您的方法被发现为 to:do: 的发件人。您的方法是否实际发送 to:do: 并不重要,内联技术只是一种优化。在更高的抽象层次上,我们都希望看到我们的方法是 to:do: 的 "sender",因此 "trick" 将其添加到文字框架中。