Skip to content

a non-ASCII uppercase letter must start a CONSTANT, not a local variable #57

Closed

Description

@tannevaled

A name beginning with a non-ASCII uppercase letter is a constant in Ruby.
The parser classifies it as an ordinary identifier, so the assignment creates a
local variable and no constant exists afterwards.

Measured against /usr/bin/ruby (2.6.10) and rbgo built from
go-embedded-ruby/ruby@main, same source both times:

FOO_Y = 1
puts defined?(FOO_Y).inspect            # both: "constant"

eval("ἍDD = 2")
puts eval('defined?(ἍDD)').inspect      # MRI: "constant"   rbgo: nil
puts Object.const_defined?(:ἍDD)        # MRI: true         rbgo: false

Ἅ is U+1F0D, GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA — an uppercase
letter outside ASCII. The lowercase side is already right: μ = 1 is a local
variable on both engines, and defined? agrees.

The rule

MRI's lexer decides constant-ness from the first character's CASE, not from its
being in A-Z. parse.y reaches tCONSTANT for any identifier whose first
character is uppercase, and for a multibyte character that question is answered
by the encoding's case table rather than by a byte range — which is why
ἍBB is a constant and μm is not.

Why it surfaced

ruby/spec's language/variables_spec.rb:885:

it 'parses a non-ASCII upcased character as a constant identifier' do
  -> { eval("def test\n  ἍBB = 1\nend") }.should.raise(SyntaxError, /dynamic constant assignment/)
end

The example needs two things that are missing in two different places: this
classification, and a dynamic constant assignment check in
go-embedded-ruby/ruby's compiler (filed there). Fixing either one alone leaves
the example failing, so this is not blocked on that one and that one is not
blocked on this.

Scope note

The companion question — which characters count as uppercase for an identifier
start — is worth settling from Ruby's own table rather than from Go's
unicode.IsUpper, since the two need not agree on titlecase characters
(Dž/dž/DŽ) or on letters with no case at all (CJK, Hebrew, Arabic), and an
identifier starting with a caseless letter is a LOCAL variable in Ruby, not a
constant. A test over one character from each of those three groups would pin it.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions