[Rpm-ecosystem] Fwd: [Rpm-maint] Fixing macro scoping

Panu Matilainen pmatilai at laiskiainen.org
Fri Feb 3 14:54:21 UTC 2017


On 02/03/2017 03:26 PM, Pascal Terjan wrote:
> On 3 February 2017 at 12:14, Panu Matilainen <pmatilai at laiskiainen.org> wrote:
>>
>> Stunned silence on rpm-maint, forwarding to rpm-ecosystem in hopes of a
>> larger and livelier audience...
>>
>>         - Panu -
>>
>> -------- Forwarded Message --------
>> Subject: [Rpm-maint] Fixing macro scoping
>> Date: Mon, 23 Jan 2017 12:30:21 +0200
>> From: Panu Matilainen <pmatilai at laiskiainen.org>
>> To: rpm-maint at lists.rpm.org <rpm-maint at lists.rpm.org>
>>
>>
>> Consider the following snippet, originating from
>> https://bugzilla.redhat.com/show_bug.cgi?id=552944:
>>
>> %{!?foo: %define foo bar}
>> %define dofoo() true
>> echo 1: %{foo}
>> %dofoo
>> echo 2: %{foo}
>>
>> I'd assume everybody agrees both %{foo}'s should evaluate to the same value,
>> but that is not the case currently. Using a cli-variant of the above:
>>
>> [pmatilai at sopuli rpm]$ rpm --define 'dofoo() true' --eval '%{!?foo: %define
>> foo bar}' --eval '%foo' --eval '%dofoo' --eval '%foo'
>> warning: Macro %foo defined but not used within scope
>>
>> bar
>> true
>> %foo
>>
>> The flaw here is that rpm supposedly implements block level scoping for
>> macros (so in the above example, "foo" would only exist in the {!?foo:...}
>> block), but doesn't actually enforce that, unless a parametric macro is
>> "called". Current rpm warns about it, but warnings or not this behavior
>> doesn't make the slightest sense.
>>
>> The question is, what do you think %{foo} should evaluate to in this case?
>>
>> Fixing it to honor the strict "block scoping" concept is not hard, now that
>> the scoping level is honored from Lua too (see
>> https://github.com/rpm-software-management/rpm/commit/1767bc4fd82bfacee622e698f9f0ae42c02126fa).
>> In this case the above reproducer would emit
>>
>> %foo
>> true
>> %foo
>>
>> Another option is slightly changing the whole scoping notion: parametric
>> macros require locally scoped macros for the automatic argument macros like
>> %#, %* and %1 anyway (it's flawed too currently, see below). So perhaps the
>> macro scoping should follow the current "call level", ie a macro defined
>> inside a parametric macro body is local to that macro, and everything else
>> is global. In this case the reproducer would emit
>> bar
>> true
>> bar
>
> My first reaction was to prefer this one because '%{!?foo: %define foo
> bar} has been used in tens of thousands of packages and sort of worked
> "forever"
> But replacing define with global is not very hard so no strong opinion...

Me too, but no strong opinion either. Also I'm biased, because

>> I have implementations for both and also a personal opinion, but I'd like to
>> hear what others think.
>>
>> The related flaw is whether locally scoped macros should be visible to
>> deeper nesting levels. Currently everything is, including those automatic
>> macros:
>>
>> $ rpm --define '%bar() Bar %#: %{?1} %{?2}' --define '%foo() Foo %#: %{?1}
>> %{?2} %bar a' --eval '%foo 1 2'
>> Foo 2: 1 2 Bar 1: a 2
>>
>> I'd consider this a bug, %2 should not be defined within %bar() since it did
>> not receive two arguments. So IMO the correct output in the above should be:
>> Foo 2: 1 2 Bar 1: a
>
> Yes that's indeed very unexpected as I have always considered %n to be
> referring to the parameters of current macros
> All %n > %# should be cleared at the same time %# is set

Yup. Although not literally *cleared*, just made invisible in the inner 
calls. You do want the original values to be there when returning from a 
nested macro call.

>> But what about a macro manually %define'd within %foo() - should that be
>> visible in %bar()?
>
> I would expect so

Interesting - I'd tend to think the contrary. I mean, considering 
"normal" languages, local variables declared within a function are only 
visible within that function. The exception to that would be nested 
function definitions where the inner function can indeed access 
everything its "parent" can, but this is conceptually different from 
call-time scope.

Maybe an example would make it clearer. I would not expect this to work 
in any remotely sane language [*]:

def bar():
     b = 1
     print(a + b)

def foo():
     a = 1
     bar()

...but this is a different story:

def foo():
     a = 1

     def bar():
         b = 1
         print(a + b)
     bar()

[*] OTOH variables in Lua have global scope unless explicitly declared 
local. A really annoying quirk in an otherwise nice and fun little language.

	- Panu -


More information about the Rpm-ecosystem mailing list