Identifier resolution, the great implementation defined mess.

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Mon Sep 22 15:39:48 PDT 2014


On Mon, Sep 22, 2014 at 02:58:33PM -0700, Walter Bright via Digitalmars-d wrote:
> On 9/22/2014 1:27 PM, deadalnix wrote:
> >On Monday, 22 September 2014 at 09:17:16 UTC, Walter Bright wrote:
> >>It is depth first. It starts at the innermost scope, which is the
> >>current scope. Somehow, we don't seem to be talking the same
> >>language :-(
> >>
> >
> >Depth first in the sense, go from the inner to the outer scope and
> >look for local symbols. If that fails, go from the inner to the outer
> >and look for imported symbols.
> 
> I would find that to be surprising behavior, as I'd expect inner
> declarations to override outer ones.

I think what makes the situation under discussion stick out like a sore
thumb, is the fact that a local import statement inserts symbols into
the current inner scope "implicitly". That is to say, when you write:

	struct S {
		int x;
		void method(int y) {
			foreach (z; 0..10) {
				import somemodule;
			}
		}
	}

the statement "import somemodule" can potentially pull in arbitrary
symbols into the inner scope, even though none of these symbols are
explicitly named in the code. If we were to write, instead:

	struct S {
		int x;
		void method(int y) {
			foreach (z; 0..10) {
				import somemodule : x, y, z;
			}
		}
	}

then it would not be surprising that x, y, z, refer to the symbols from
somemodule, rather than the loop variable, method parameter, or member
variable.

However, in the former case, the user is left at the mercy of somemodule
(keep in mind that this can be a 3rd party module over which the user
has little control) what symbols will be introduced into the inner
scope. If somemodule defines a symbol 'x', then it silently overrides
S.x in the inner scope. But since 'x' is implicit from the import
statement, a casual perusal of the code would give the wrong impression
that 'x' refers to S.x, whereas it actually refers to somemodule.x.

This problem is intrinsically the same problem exhibited by shadowing of
local variables:

	void func(int x) {
		int x;
		{
			int x;
			x++;
		}
	}

which is rejected by the compiler. Both result in code that is difficult
to reason about and error-prone because of symbol ambiguity.

Arguably, we should reject such import statements if it would introduce
this kind of symbol shadowing. But regardless of whether we decide to do
that or not, it's becoming clear that unqualified local imports are a
bad idea, and I, for one, will avoid using it for the above reasons.
Instead, I'd recommend only using qualified local imports:

	void func(int x, int z) {
		import submodule : x, y;
		// I explicitly want submodule.x to shadow parameter x;
		// I also name submodule.y so that in the event that
		// submodule actually contains a symbol 'z', it does not
		// accidentally shadow parameter z.
	}

leaving unqualified imports to the module global scope. In an ideal
world, the language would enforce this usage.


T

-- 
Life would be easier if I had the source code. -- YHL


More information about the Digitalmars-d mailing list