September 21, 2014
On 9/20/2014 10:29 PM, deadalnix wrote:
> DMD does very bizarre things. I think I should write a DIP, but time is always
> running low...
>
> Free goodie: when you import, all symbol are resolved via the expected import
> resolution mechanism. All ? No, the root package is imported in the local scope.
>
> foo(int a) {
>    import a.b.c;
>    // a is now a package and not the parameter a anymore.
> }

What's bizarre about it? You declared a package symbol 'a' in the local scope with the import declaration.

The way imports (and mixin templates) work for symbol lookup is completely consistent.

You could reasonably argue that since package 'a' shadows parameter 'a' in the same way that this issues an error:

  foo(int a) {
    double a;
  }

but, again, there is nothing bizarre about the import name lookup.


Lookup rules are straightforward:

  scope is current scope
  do {
      look up name in scope
      if name is found, done!
      look up name in imports imported into scope
      if name is found, done!
      set scope to enclosing scope
   } while scope exists

I don't know what mental model people have for how lookups work, but the above algorithm is how it actually works.
September 21, 2014
On 9/21/2014 12:58 PM, Timon Gehr wrote:
> On 09/21/2014 09:54 PM, Walter Bright wrote:
>> On 9/21/2014 5:55 AM, Timon Gehr wrote:
>>> For local imports, DMD imports _all_ symbols into the local scope,
>>> shadowing
>>> anything that was there, which is plain broken (as Sӧnke's example
>>> shows). BTW:
>>> how do you suggest to treat the root package? I think importing into
>>> the local
>>> scope is fine, but the above example should emit an error because the
>>> parameter
>>> and the package conflict.
>>>
>>> (The following code does not compile either:
>>> int std;
>>> import std.conv;)
>>
>> Of course it shouldn't, ...
>
> (That was my point.)

Parameters are not in the same scope as local variables.
September 21, 2014
On Sunday, 21 September 2014 at 20:05:57 UTC, Walter Bright wrote:
> I don't know what mental model people have for how lookups work, but the above algorithm is how it actually works.

My mental model for local imports is "it's the same as module level imports, except the symbols are only available in this scope". I wouldn't expect a module symbol to shadow a local symbol.
September 21, 2014
On Sun, 21 Sep 2014 13:04:49 -0700
Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote:

> I don't know what mental model people have for how lookups work, but the above algorithm is how it actually works.
i believe that people expect this:

  void foo (int a) {
    import a;
    a.bar(); // here we use 'a' module
    xyzzy(); // it's actually 'a.xyzzy', and 'a as module' is implicit
    writeln(a); // here we use 'int a' argument
  }

i.e. symbol resolver will try argument/local name first, and only if it failed tries to search in module.

or, more complicated sample:

  struct A { int bar; int baz; }
  void foo (in A a) {
    import a;
    a.bar(); // KABOOM, conflicting names
    xyzzy(); // it's actually 'a.xyzzy', and 'a as module' is implicit
    writeln(a); // KABOOM (both module and arg are complex types
    writeln(a.baz); // it's ok until module 'a' doesn't have 'baz'
  }

i'm not saying that this is how things *must* work, but this is what one excepts, i think.


September 21, 2014
On 09/21/2014 10:08 PM, Walter Bright wrote:
> On 9/21/2014 12:58 PM, Timon Gehr wrote:
>> On 09/21/2014 09:54 PM, Walter Bright wrote:
>>> On 9/21/2014 5:55 AM, Timon Gehr wrote:
>>>> For local imports, DMD imports _all_ symbols into the local scope,
>>>> shadowing
>>>> anything that was there, which is plain broken (as Sӧnke's example
>>>> shows). BTW:
>>>> how do you suggest to treat the root package? I think importing into
>>>> the local
>>>> scope is fine, but the above example should emit an error because the
>>>> parameter
>>>> and the package conflict.
>>>>
>>>> (The following code does not compile either:
>>>> int std;
>>>> import std.conv;)
>>>
>>> Of course it shouldn't, ...
>>
>> (That was my point.)
>
> Parameters are not in the same scope as local variables.

I know, and you will know that it makes no practical difference since identifier shadowing is disallowed (deprecated) within a function.
September 21, 2014
On Sunday, 21 September 2014 at 20:05:57 UTC, Walter Bright wrote:
> On 9/20/2014 10:29 PM, deadalnix wrote:
>> DMD does very bizarre things. I think I should write a DIP, but time is always
>> running low...
>>
>> Free goodie: when you import, all symbol are resolved via the expected import
>> resolution mechanism. All ? No, the root package is imported in the local scope.
>>
>> foo(int a) {
>>   import a.b.c;
>>   // a is now a package and not the parameter a anymore.
>> }
>
> What's bizarre about it? You declared a package symbol 'a' in the local scope with the import declaration.
>

Because this is not consistent with module level import and that is not consistent with how local shadowing works.

IMO you should fallback to import if local lookup failed.
September 21, 2014
On 09/22/2014 01:37 AM, deadalnix wrote:
> On Sunday, 21 September 2014 at 20:05:57 UTC, Walter Bright wrote:
>> On 9/20/2014 10:29 PM, deadalnix wrote:
>>> DMD does very bizarre things. I think I should write a DIP, but time
>>> is always
>>> running low...
>>>
>>> Free goodie: when you import, all symbol are resolved via the
>>> expected import
>>> resolution mechanism. All ? No, the root package is imported in the
>>> local scope.
>>>
>>> foo(int a) {
>>>   import a.b.c;
>>>   // a is now a package and not the parameter a anymore.
>>> }
>>
>> What's bizarre about it? You declared a package symbol 'a' in the
>> local scope with the import declaration.
>>
>
> Because this is not consistent with module level import

Yes it is.

> and that is not consistent with how local shadowing works.

Indeed.

>
> IMO you should fallback to import if local lookup failed.

The package symbol is not imported, it is introduced by imports. This is true in module scopes and local scopes alike.
September 22, 2014
On 9/21/2014 2:17 PM, ketmar via Digitalmars-d wrote:
> On Sun, 21 Sep 2014 13:04:49 -0700
> Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
>> I don't know what mental model people have for how lookups work, but
>> the above algorithm is how it actually works.
> i believe that people expect this:
>
>    void foo (int a) {
>      import a;
>      a.bar(); // here we use 'a' module
>      xyzzy(); // it's actually 'a.xyzzy', and 'a as module' is implicit
>      writeln(a); // here we use 'int a' argument

Context dependent lookups? That's an awful lot more complex than the existing rules.

>    }
>
> i.e. symbol resolver will try argument/local name first, and only if it
> failed tries to search in module.

That's how it does work. It's just that parameters are in an enclosing scope.


> or, more complicated sample:
>
>    struct A { int bar; int baz; }
>    void foo (in A a) {
>      import a;
>      a.bar(); // KABOOM, conflicting names
>      xyzzy(); // it's actually 'a.xyzzy', and 'a as module' is implicit
>      writeln(a); // KABOOM (both module and arg are complex types
>      writeln(a.baz); // it's ok until module 'a' doesn't have 'baz'
>    }
>
> i'm not saying that this is how things *must* work, but this is what
> one excepts, i think.

I have no idea how to even write such rules, let alone what kind of error messages to generate when the user does it wrong.

I believe it is far better to have simple rules, easy to explain, and have a few awkward edge cases than having a terribly complex setup with special cases that nobody understands.

For example, probably 3 people on the planet understand C++ overloading rules (pages and pages of trivia). The rest just try things at random until it appears to work.

September 22, 2014
On 9/21/2014 4:37 PM, deadalnix wrote:
> On Sunday, 21 September 2014 at 20:05:57 UTC, Walter Bright wrote:
>> On 9/20/2014 10:29 PM, deadalnix wrote:
>>> DMD does very bizarre things. I think I should write a DIP, but time is always
>>> running low...
>>>
>>> Free goodie: when you import, all symbol are resolved via the expected import
>>> resolution mechanism. All ? No, the root package is imported in the local scope.
>>>
>>> foo(int a) {
>>>   import a.b.c;
>>>   // a is now a package and not the parameter a anymore.
>>> }
>>
>> What's bizarre about it? You declared a package symbol 'a' in the local scope
>> with the import declaration.
>>
>
> Because this is not consistent with module level import

Yes, it is.

> and that is not consistent with how local shadowing works.

You can argue that 'a' should be an error to shadow the parameter 'a'. But extending this to the contents of the import is not consistent.


> IMO you should fallback to import if local lookup failed.

That's exactly how it does work. It's the SAME code that implements it.

Your misunderstanding appears to be that:

   foo(int a) { int b; }

'a' and 'b' are in the same scope. They are NOT in the same scope.
September 22, 2014
On Monday, 22 September 2014 at 06:05:42 UTC, Walter Bright wrote:
> On 9/21/2014 2:17 PM, ketmar via Digitalmars-d wrote:
>> On Sun, 21 Sep 2014 13:04:49 -0700
>> Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>>
>>> I don't know what mental model people have for how lookups work, but
>>> the above algorithm is how it actually works.
>> i believe that people expect this:
>>
>>   void foo (int a) {
>>     import a;
>>     a.bar(); // here we use 'a' module
>>     xyzzy(); // it's actually 'a.xyzzy', and 'a as module' is implicit
>>     writeln(a); // here we use 'int a' argument
>
> Context dependent lookups? That's an awful lot more complex than the existing rules.
>
>>   }
>>
>> i.e. symbol resolver will try argument/local name first, and only if it
>> failed tries to search in module.
>
> That's how it does work. It's just that parameters are in an enclosing scope.
>
>
>> or, more complicated sample:
>>
>>   struct A { int bar; int baz; }
>>   void foo (in A a) {
>>     import a;
>>     a.bar(); // KABOOM, conflicting names
>>     xyzzy(); // it's actually 'a.xyzzy', and 'a as module' is implicit
>>     writeln(a); // KABOOM (both module and arg are complex types
>>     writeln(a.baz); // it's ok until module 'a' doesn't have 'baz'
>>   }
>>
>> i'm not saying that this is how things *must* work, but this is what
>> one excepts, i think.
>
> I have no idea how to even write such rules, let alone what kind of error messages to generate when the user does it wrong.
>
> I believe it is far better to have simple rules, easy to explain, and have a few awkward edge cases than having a terribly complex setup with special cases that nobody understands.
>
> For example, probably 3 people on the planet understand C++ overloading rules (pages and pages of trivia). The rest just try things at random until it appears to work.

+1

We should simply do a lookup for local symbol, and if that fail, imported symbols.

In that case, a should resolve as the parameter, all the time.