Fuzion Logo
flang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

Visibility

The visibility of a feature decides where this feature can be accessed.

Scopes

A scope is syntactically defined by a block of statements enclosed by braces { } or limited by indentation. Scopes can be nested, e.g., the declaration of an inner feature is a statement in a surrounding block or the block in an if-statement is nested in the block that contains the if statement.

Unqualified Visibility

Routines

The unqualified visibility of a routine determines where this routine can be called directly without a qualifying target t (as in t.f. A routine f declared in scope s is visible for an unqualified call in s itself, all nested scopes of s and all inner routines declared in the same scope s or in any nested scope within s:

  a1
  {
    b1
    {
      c1
      {
        // ... can make unqualified call to a1, a2, b1, b2, c1, c2 ...
      }

      c2
      {
        // ... can make unqualified call to a1, a2, b1, b2, c1, c2 ...
      }
    }

    b2
    {
      c3
      {
        // ... can make unqualified call to a1, a2, b1, b2, c3, c4 ...
      }

      c4
      {
        // ... can make unqualified call to a1, a2, b1, b2, c3, c4 ...
      }
    }
  }

  a2
  {
    b3
    {
      c5
      {
        // ... can make unqualified call to a1, a2, b3, c5, c6 ...
      }

      c6
      {
        // ... can make unqualified call to a1, a2, b3, c5, c6 ...
      }
    }
  }

Fields

Compared to routines, fields are not visible for unqualified calls before their declaration:

  a1
  {
    f1 := <expr1>;
    // ... can make unqualified call to f1 ...
    b1
    {
      // ... can make unqualified call to f1 ...
      f2 := <expr2>;
      // ... can make unqualified call to f1, f2 ...
      c1
      {
        // ... can make unqualified call to f1, f2 ...
        f3 := <expr3>;
        // ... can make unqualified call to f1, f2, f3 ...
        f4 := <expr4>;
        // ... can make unqualified call to f1, f2, f4 ...
      }
      // ... can make unqualified call to f1, f2 ...

      c2
      {
        // ... can make unqualified call to f1, f2 ...
        f5 := <expr5>;
        // ... can make unqualified call to f1, f2, f5 ...
        f6 := <expr6>;
        // ... can make unqualified call to f1, f2, f6 ...
      }
      // ... can make unqualified call to f1, f2 ...
      f7 := <expr7>;
      // ... can make unqualified call to f1, f2, f7 ...
    }
    // ... can make unqualified call to f1 ...
    f8 := <expr8>;
    // ... can make unqualified call to f1, f8 ...

    b2
    {
      // ... can make unqualified call to f1, f8 ...
      f9 := <expr9>;
      // ... can make unqualified call to f1, f8, f9 ...
    }
    // ... can make unqualified call to f1, f8 ...
  }

Assignment Visibility

Unqualified mutuable fields can be the target of an assignment whenever the field is visible for an unqualified call.

Masking

Masking can make a field inaccessible by replacing it by a field with equal name defined in the same or inner scope.

Masking in Inner Scopes

Features in an inner scope can use the same name as those in an outer scope, effectively masking the outer features:

  a
  {
    x => "Outer!"
    b
    {
      x => "Inner!"
      stdout.println(x)  // prints "Inner!"

      c
      {
        stdout.println(x)  // prints "Inner!"
      }
    }
    stdout.println(x)  // prints "Outer!"
  }

Outer fields can nevertheless be accessed via a qualified access as follows

  a
  {
    x => "Outer!"
    b
    {
      x => "Inner!"
      stdout.println(x)         // prints "Inner!"
      stdout.println(b.this.x)  // prints "Inner!"
      stdout.println(a.this.x)  // prints "Outer!"

      c
      {
        stdout.println(x)         // prints "Inner!"
        stdout.println(b.this.x)  // prints "Inner!"
        stdout.println(a.this.x)  // prints "Outer!"
      }
    }
    stdout.println(x)         // prints "Outer!"
    stdout.println(b.this.x)  // *** illegal, compiler error
    stdout.println(a.this.x)  // prints "Outer!"
  }

Masking Fields by Declarations

For convenience, the field declaration operation allows to mask fields declared previously in the same scope:

  x := 2;        stdout.println("x is {x}")  // will print i32: x is 2
  x := x as f64; stdout.println("x is {x}")  // will print f64: x is 2.0
  x := x * 3.14; stdout.println("x is {x}")  // will print f64: x is 6.28
  x := x as i32; stdout.println("x is {x}")  // will print i32: x is 6

This example declares three different fields with the name x and different ranges of visibility. Note that a field declaration does not mask the field during the evaluation of the expression of the newly declared field's initial value. I.e., in the second declaration above x := x as f64, the x on the right hand side of the assignment refers to the x declared in the previous line.

Some limitations will have to be put on masking fields by declarations to reduce confuzion:

Masks fields of the same scope may nevertheless outlive their scope at runtime, as in this example:

  x := 2;        f := fun => stdout.println("x is {x}")
  x := x as f64; g := fun => stdout.println("x is {x}")
  x := x * 3.14; h := fun => stdout.println("x is {x}")
  x := x as i32; i := fun => stdout.println("x is {x}")
  f(); g(); h(); i()

This will produce the same output as the previous example, but the four different fields with name x will be kept during runtime.

Features declared in inner Blocks

Features declared in inner blocks have visibility restrictions similar to those declared in inner features:

  a { }
  if cond
    {
      // ... can make unqualified call to a, b ...
      b { }
      // ... can make unqualified call to a, b ...
    }
  else
    {
      // ... can make unqualified call to a, c ...
      c { }
      // ... can make unqualified call to a, c ...
    }
  // ... can make unqualified call to a ...

The visibility in loops is a bit more complicated, as shown in this example:

  a { }
  for
    ix1 := 5, ix + 1          // ... can make unqualified call to a, ix1 ...
    ix2 := 3 * ix1, ix2 + 3   // ... can make unqualified call to a, ix1, ix2 ...
    it1 in set                // ... can make unqualified call to a, ix1, ix2 ...
    it2 in 0..100             // ... can make unqualified call to a, ix1, ix2, it1 ...
    ix3 = it1 + it2           // ... can make unqualified call to a, ix1, ix2, it1, it2 ...
  while // ... can make unqualified call to a, ix1, ix2, it1, it2 ...
    {
      b { }
      // ... can make unqualified call to a, ix1, ix2, it1, it2, b ...
    }
  until // ... can make unqualified call to a, ix1, ix2, it1, it2, b ...
    {
      // ... can make unqualified call to a, ix1, ix2, it1, it2, b ...
    }
  else
    {
      // ... can make unqualified call to a, ix1, ix2 ...
    }
  // ... can make unqualified call to a ...

In a loop, within the for-block, index variables that are not iterators can be accessed after their declaration and in the next-part of their declaration (after the comma). Index variables are accessible in the while- and until-blocks. Features declared in the while-block are also accessible in the until-block. The else block can access all index variables before the first iterator index variable (one that uses in in the for-block)

The reason for this complex visibility is that it is guaranteed that a for-block is executed fully before the while block is entered. Additionally, the until-block can only be entered after the while block was fully executed.

On the other hand, the else block may be entered as soon as an iterator index variable reaches the end of the iterated set, so only previously declared index variables are guaranteed to be set.

Qualified Visibility

The qualified visibility determines if a feature f is visible via a qualified access target.f.

Qualified Call Visibility

TBD:

Qualified Assignment Visibility

Qualified assignments are not allowed:

  a
  {
    b
    {
      x := 3
    }

    v := b
    b.x = 5  // *** illegal, compiler error
  }

Even qualified assignments to masked outer fields are not allowed:

  a
  {
    x := 3
    y := 4

    b
    {
      x := 5
      x = 6         // ok, a.b.x is set to 6
      y = 7         // ok, a.y is set to 7
      a.this.x = 8  // *** illegal, compiler error
      a.this.y = 9  // *** illegal, compiler error
    }
  }

Module Visibility

A feature declared in another module is visible to other modules only if it is exported from the modules. TBD: Need to decide how to do this, mark it export or similar.

An exported feature f declared in scope s of a module m is visible for an unqualified call in another module m2 if that call happens in a scope s2 that is an inner scope of s and it was not masked.

Unqualified Visibility

Example: In module A:

  export a
  {
    export x { }
    export b
    {
      export y { }
      public z { }
    }
    public c
    {
      export y { }  // *** illegal, compiler error: cannot export y since outer c is not exported
      public z { }
    }
  }

In module B:

  a.b.newFeatureInB
  {
    // ... x and y are visible here, c and z are not ...
  }

  a.c.newFeatureInB // *** illegal, compiler error: c not exported
  {
    // ...
  }

Qualified Visibility

Qualified accesses to features of another module are restricted to the features that are exported from that module.

Module Assignment Visibility

No fields declared in another module are visible as the target of an assignment in another module.

Visibility for Inheritance

Some languages treat visibility for heir of a class different to the visibility for users: Java knows protected members and has just introduced sealed classes JEP 360.

For Fuzion, the question is whether it would be useful to have a means to make a feature visibile as a type without allowing calls to the feature, in particular without allowing calls in an inheritance clause.

TBW: Need to decide if this is important enough and if so, do we need specific language support for this or can this be modelled differently (e.g., by adding a unit type argument to a feature that is made visible only to the permitted heirs)?