Skip to content

Commit

Permalink
Add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Akirathan committed Oct 2, 2023
1 parent 0fee9cd commit a30993a
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ This specification is broken down into the following sections:
those of suspended computations.
- [**Modules:**](./modules.md) The semantics of Enso's modules.
- [**Scoping:**](./scoping.md) Enso's scoping and identifier resolution rules.
- [**Encapsulation:**](./encapsulation.md) The semantics of Enso's encapsulation
system - access restriction.
159 changes: 159 additions & 0 deletions docs/semantics/encapsulation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
layout: developer-doc
title: Diagnostics
category: semantics
tags: [semantics, diagnostics, runtime]
order: 11
---

# Encapsulation

_Encapsulation_ is the system of hiding certain internal **entities** (modules,
types, methods, constructors, fields) in one project/library from other
projects/libraries. This document is an excerpt from the specification at
https://github.com/orgs/enso-org/discussions/7088.

## Requirements

- Be able to hide an entity on demand. By hiding, we mean that the entity cannot
be directly imported, and that it cannot be used via FQN.
- i.e. an entity shall be hidden both during compile time (project
compilation), and during runtime.
- Entity being hidden at runtime implies that it does not have any entry in
the Suggestion database, therefore, no entry in the Component browser.
- Be able to import all public symbols from a library with
`from Library import all`.
- Be able to import a selected set of symbols from a library with
`from Library import Symbol_1, Symbol_2, ...`.
- Import a public symbol directly with
`import Library.Public_Module.Public_Type`.
- Use a public symbol via FQN: `Library.Public_Module.Public_Type`.

## Implementation

Let's introduce a `private` keyword. By prepending (syntax rules discussed
below) `private` keyword` to an entity, we declare it as **project private**. A
project-private entity is an entity that can be imported and used in the same
project, but cannot be imported nor used in different projects. Note that it is
not desirable to declare the entities as _module private_, as that would be too
restrictive, and would prevent library authors using the entity within the
project.

From now on, let's consider _project-private_ and _private_ synonymous, and
**public** as an entity that is not private.

## Syntax

All the entities, except modules, shall be declared private by prepending them
with `private` keyword. Declaring a module as private shall be done be writing
the `private` keyword at the very beginning of the module, before all the import
statements, ignoring all the comments before. Fields cannot have `private`
keyword, only constructors.

## Semantics

### Submodules

A hierarchy of submodules cannot mix public and private modules. In other words,
if a module is public, its whole subtree must be public as well. For example,
having a public module `A` and private submodule `A.B` is forbidden and shall be
reported as an error during compilation.

## Example

Lib/src/Pub_Type.enso:

```
type Pub_Type
Constructor field
private priv_method self = ...
pub_method self = self.field.to_text
private type Priv_Type
```

Lib/src/Methods.enso:

```
pub_stat_method x y = x + y
private priv_stat_method x y = x - y
```

Lib/src/Internal/Helpers.enso:

```
# Mark the whole module as private
private
# OK to import private types in the same project
import project.Pub_Type.Priv_Type
```

Lib/src/Main.enso:

```
import project.Pub_Type.Pub_Type
export project.Pub_Type.Pub_Type
import project.Pub_Type.Priv_Type # OK - we can import private types in the same project.
export project.Pub_Type.Priv_Type # Failes at compile time - re-exporting private types is forbidden.
```

tmp.enso:

```
from Lib import Pub_Type
import Lib.Pub_Type.Priv_Type # Fails during compilation
import Lib.Methods
main =
# This constructor is not private, we can use it here.
obj = Pub_Type.Constructor field=42
obj.field # OK - Constructor is public, therefore, field is public
obj.priv_method # Runtime failure - priv_method is private
Pub_Type.priv_method self=obj # Runtime failure
obj.pub_method # OK
Lib.Pub_Type.Priv_Type # Fails at runtime - accessing private types via FQN is forbidden
Methods.pub_stat_method 1 2 # OK
Methods.priv_stat_method # Fails at runtime
```

## Checks

There shall be two checks. One check during **compilation**, that can be
implemented as a separate compiler pass, and that will ensure that no private
entity is _re-exported_ (exported from a module that is different from the
module inside which the entity is defined) and that for every type it holds that
either all the constructors are public or all the constructors are private

The second check shall be done during the **method/name resolution** step. This
step happens at runtime, before a method is called. After the method is
resolved, there shall be no further checks, so that the peak performance is not
affected.

## Performance impact

The performance hit on compilation time is minimal, as there are already dozens
of different compiler passes. Moreover, in the new compiler pass we shall check
only imports and exports statements, no other IR.

The performance hit on runtime, during method resolution, is minimal as well,
because it can be as easy as additional lookup in a hash map. Peak performance
will not be affected at all, as there are no further checks after method
resolution.

## Overcoming encapsulation

Sometimes it is useful to be able to access internal entities. Testing is the
most obvious example. Let's introduce a new CLI flag to the Engine launcher
called `--disable-private-check`, which will disable all the private checks
during compilation and method resolution.

## Other notes

- A private module implies that all the entities defined within are private
- A private type implies that all the constructors, methods and fields defined
within are private
- A private constructor implies private fields defined in that constructor.

0 comments on commit a30993a

Please sign in to comment.