Почему Ruby сохраняет оценку кода после создания исключения NameError?

простой код, который я не могу объяснить себе:

puts a if a = 1

в результате

warning: found = in conditional, should be ==
NameError: undefined local variable or method 'a' for main:Object

хотя, теперь при проверке a мы можем видеть, что он был определен:

a #=> 1

почему a назначается 1 несмотря на исключение?

С docs:

путаница начинается с неупорядоченное выполнение выражения. Сначала местный переменная назначается-затем вы пытаетесь вызвать a несуществующий метод [a].

эта часть все еще запутывает-почему интерпретатор не обнаруживает уже определен локальная переменная a и все еще пытается вызвать" несуществующий " метод? Если он также не проверяет локальные переменные, найдите определенную локальную переменную a и печать 1?

2 ответов


давайте посмотрим на абстрактное дерево синтаксиса Ruby для модификатора if:

$ ruby --dump=parsetree -e 'puts a if a = 1'

# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,15))
# +- nd_tbl: :a
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,15))
#     +- nd_head:
#     |   (null node)
#     +- nd_body:
#     |   @ NODE_IF (line: 1, code_range: (1,0)-(1,15))
#     |   +- nd_cond:
#     |   |   @ NODE_DASGN_CURR (line: 1, code_range: (1,10)-(1,15))
#     |   |   +- nd_vid: :a
#     |   |   +- nd_value:
#     |   |       @ NODE_LIT (line: 1, code_range: (1,14)-(1,15))
#     |   |       +- nd_lit: 1
#     |   +- nd_body:
#     |   |   @ NODE_FCALL (line: 1, code_range: (1,0)-(1,6))
#     |   |   +- nd_mid: :puts
#     |   |   +- nd_args:
#     |   |       @ NODE_ARRAY (line: 1, code_range: (1,5)-(1,6))
#     |   |       +- nd_alen: 1
#     |   |       +- nd_head:
#     |   |       |   @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
#     |   |       |   +- nd_mid: :a
#     |   |       +- nd_next:
#     |   |           (null node)
#     |   +- nd_else:
#     |       (null node)
#     +- nd_compile_option:
#         +- coverage_enabled: false

и if:

$ ruby --dump=parsetree -e 'if a = 1 then puts a end'

# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,24))
# +- nd_tbl: :a
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,24))
#     +- nd_head:
#     |   (null node)
#     +- nd_body:
#     |   @ NODE_IF (line: 1, code_range: (1,0)-(1,24))
#     |   +- nd_cond:
#     |   |   @ NODE_DASGN_CURR (line: 1, code_range: (1,3)-(1,8))
#     |   |   +- nd_vid: :a
#     |   |   +- nd_value:
#     |   |       @ NODE_LIT (line: 1, code_range: (1,7)-(1,8))
#     |   |       +- nd_lit: 1
#     |   +- nd_body:
#     |   |   @ NODE_FCALL (line: 1, code_range: (1,14)-(1,20))
#     |   |   +- nd_mid: :puts
#     |   |   +- nd_args:
#     |   |       @ NODE_ARRAY (line: 1, code_range: (1,19)-(1,20))
#     |   |       +- nd_alen: 1
#     |   |       +- nd_head:
#     |   |       |   @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
#     |   |       |   +- nd_vid: :a
#     |   |       +- nd_next:
#     |   |           (null node)
#     |   +- nd_else:
#     |       (null node)
#     +- nd_compile_option:
#         +- coverage_enabled: false

единственное отличие-аргумент метода для puts:

#     |   |       |   @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
#     |   |       |   +- nd_mid: :a

vs:

#     |   |       |   @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
#     |   |       |   +- nd_vid: :a

с модификатором if, парсер обрабатывает a как вызов метода и создает NODE_VCALL. Это указывает интерпретатору сделать вызов метода (хотя там и локальная переменная a), в результате NameError. (потому что нет метода a)

стандартные if, парсер обрабатывает a как локальная переменная и создает NODE_DVAR. Это указывает интерпретатору искать локальную переменную, которая работает так, как ожидалось.

как вы можете видеть, Ruby распознает локальные переменные на уровне парсера. Вот почему в документации говорится: (курсив добавлен)

модификатор и стандартные версии [...] не являются точными преобразования друг друга из-за разбора заказа.


Ruby анализирует код слева направо. Локальные переменные определяются при анализе первого назначения им. At puts a нет задание a был проанализирован еще, таким образом, локальная переменная a еще не существует, и Ruby предполагает a - это вызов метода. Локальная переменная существует только в право и ниже задание.

во время выполнения Ruby должен оценить условие, чтобы выяснить, следует ли выполнять puts, так что a инициализируется к 1.

вы, похоже, выполняете этот код в каком-то REPL. Обычно repls спасает исключения вместо завершения, поэтому ваш код продолжает выполняться вместо завершения, и поскольку мы теперь ниже назначения, переменная определена, и поскольку назначение было выполнено, переменная инициализируется.

если различие между определение и инициализации of переменная вам непонятна, задумайтесь над этим:

foo
# NameError

if false
  foo = 42
end

foo
#=> nil

foo = :bar

foo
#=> :bar