mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Atom constructors can be private (#9692)
Closes #8836. Atom constructors can be declared as private (project-private). project-private constructors can be called only from the same project. See the encapsulation.md docs for more info. --------- Co-authored-by: Jaroslav Tulach <jaroslav.tulach@enso.org> Co-authored-by: Radosław Waśko <radoslaw.wasko@enso.org> Co-authored-by: Hubert Plociniczak <hubert.plociniczak@gmail.com> Co-authored-by: Kaz Wesley <kaz@lambdaverse.org>
This commit is contained in:
parent
16e1999040
commit
660c5e7a9d
@ -1127,6 +1127,7 @@
|
||||
- [Autoscoped constructors][9190]
|
||||
- [Allow Oracle GraalVM JDK][9322]
|
||||
- [`Table.join` can access its `right` argument][9410]
|
||||
- [Atom constructors can be project-private][9692]
|
||||
|
||||
[3227]: https://github.com/enso-org/enso/pull/3227
|
||||
[3248]: https://github.com/enso-org/enso/pull/3248
|
||||
@ -1287,6 +1288,7 @@
|
||||
[8867]: https://github.com/enso-org/enso/pull/8867
|
||||
[9190]: https://github.com/enso-org/enso/pull/9190
|
||||
[9322]: https://github.com/enso-org/enso/pull/9322
|
||||
[9692]: https://github.com/enso-org/enso/pull/9692
|
||||
[9410]: https://github.com/enso-org/enso/pull/9410
|
||||
|
||||
# Enso 2.0.0-alpha.18 (2021-10-12)
|
||||
|
@ -307,6 +307,32 @@ type Not_Invokable
|
||||
to_display_text : Text
|
||||
to_display_text self = "Type error: expected a function, but got "+self.target.to_display_text+"."
|
||||
|
||||
@Builtin_Type
|
||||
type Private_Access
|
||||
## PRIVATE
|
||||
An error that occurs when a private (project-private) method is called from a different
|
||||
project.
|
||||
|
||||
Arguments:
|
||||
- this_project_name: Optional qualified name of the current (caller) project.
|
||||
- target_project_name: Optional qualified name name of the project of callee.
|
||||
- target_method_name: The name of the target method call that is project-private and thus cannot be called.
|
||||
Error (this_project_name : (Text | Nothing)) (target_project_name : (Text | Nothing)) (target_method_name : Text)
|
||||
|
||||
## PRIVATE
|
||||
Convert the Private_Access error to a human-readable format.
|
||||
to_display_text : Text
|
||||
to_display_text self =
|
||||
this_proj = if self.this_project_name.is_nothing then "unknown project" else "project '"+self.this_project_name+"'"
|
||||
target_proj = if self.target_project_name.is_nothing then "unknown project" else "project '"+self.target_project_name+"'"
|
||||
"Private access error: The project-private method '"
|
||||
+ self.target_method_name
|
||||
+ "' in "
|
||||
+ target_proj
|
||||
+ " cannot be accessed from "
|
||||
+ this_proj
|
||||
+ "."
|
||||
|
||||
@Builtin_Type
|
||||
type Unsupported_Argument_Types
|
||||
## PRIVATE
|
||||
|
@ -78,6 +78,11 @@ Methods on types (or on modules) can be specified private. To check whether a
|
||||
private method is accessed only from within the same project, a runtime check
|
||||
must be performed, as this cannot be checked during the compilation.
|
||||
|
||||
### Polyglot access
|
||||
|
||||
No polyglot foreign code can access private entities. For all the foreign code,
|
||||
private entities are not visible.
|
||||
|
||||
## Example
|
||||
|
||||
Lib/src/Pub_Type.enso:
|
||||
@ -87,8 +92,6 @@ type Pub_Type
|
||||
Constructor field
|
||||
private priv_method self = ...
|
||||
pub_method self = self.field.to_text
|
||||
|
||||
private type Priv_Type
|
||||
```
|
||||
|
||||
Lib/src/Methods.enso:
|
||||
@ -104,8 +107,6 @@ 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:
|
||||
@ -113,17 +114,14 @@ 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
|
||||
import Lib.Internal.Helpers # Fails during compilation - cannot import private module
|
||||
|
||||
main =
|
||||
# This constructor is not private, we can use it here.
|
||||
@ -133,8 +131,6 @@ main =
|
||||
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
|
||||
```
|
||||
|
@ -16,6 +16,9 @@ class Function(val value: Value) {
|
||||
*/
|
||||
def execute(args: AnyRef*): Value = value.execute(args: _*)
|
||||
|
||||
/** Helper method for java that just delegates to the other execute method */
|
||||
def execute(): Value = value.execute()
|
||||
|
||||
/** Checks function equality by checking the identity of the underlying
|
||||
* objects.
|
||||
*
|
||||
|
@ -9,6 +9,8 @@ import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.enso.compiler.core.IR;
|
||||
import org.enso.compiler.core.ir.MetadataStorage;
|
||||
import org.enso.compiler.pass.IRPass.IRMetadata;
|
||||
import org.enso.compiler.pass.resolve.DocumentationComments;
|
||||
|
||||
/**
|
||||
* Represents a node in the GraphViz graph.
|
||||
@ -84,6 +86,9 @@ record GraphVizNode(
|
||||
private Map<String, String> additionalAttrs = new HashMap<>();
|
||||
private Object object;
|
||||
|
||||
private static final List<Class<? extends IRMetadata>> metadataToSkip =
|
||||
List.of(DocumentationComments.Doc.class);
|
||||
|
||||
static Builder fromObject(Object obj) {
|
||||
var className = className(obj);
|
||||
var id = Utils.id(obj);
|
||||
@ -129,8 +134,10 @@ record GraphVizNode(
|
||||
ir.passData()
|
||||
.map(
|
||||
(pass, metadata) -> {
|
||||
var metaName = metadata.metadataName();
|
||||
bldr.addLabelLine(" - " + metaName);
|
||||
if (!metadataToSkip.contains(metadata.getClass())) {
|
||||
var metaName = metadata.metadataName();
|
||||
bldr.addLabelLine(" - " + metaName);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
|
@ -0,0 +1,91 @@
|
||||
package org.enso.compiler.pass.analyse;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.enso.compiler.context.InlineContext;
|
||||
import org.enso.compiler.context.ModuleContext;
|
||||
import org.enso.compiler.core.IR;
|
||||
import org.enso.compiler.core.ir.Expression;
|
||||
import org.enso.compiler.core.ir.Module;
|
||||
import org.enso.compiler.core.ir.expression.errors.Syntax;
|
||||
import org.enso.compiler.core.ir.expression.errors.Syntax.InconsistentConstructorVisibility$;
|
||||
import org.enso.compiler.core.ir.module.scope.Definition;
|
||||
import org.enso.compiler.pass.IRPass;
|
||||
import scala.collection.immutable.Seq;
|
||||
import scala.jdk.javaapi.CollectionConverters;
|
||||
|
||||
/**
|
||||
* Ensures that all type definitions have either all constructors public, or all constructors
|
||||
* private.
|
||||
*/
|
||||
public final class PrivateConstructorAnalysis implements IRPass {
|
||||
public static final PrivateConstructorAnalysis INSTANCE = new PrivateConstructorAnalysis();
|
||||
|
||||
private UUID uuid;
|
||||
|
||||
private PrivateConstructorAnalysis() {}
|
||||
|
||||
@Override
|
||||
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
|
||||
this.uuid = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID key() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seq<IRPass> precursorPasses() {
|
||||
List<IRPass> passes = List.of(PrivateModuleAnalysis.INSTANCE);
|
||||
return CollectionConverters.asScala(passes).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seq<IRPass> invalidatedPasses() {
|
||||
Object obj = scala.collection.immutable.Nil$.MODULE$;
|
||||
return (scala.collection.immutable.List<IRPass>) obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Module runModule(Module ir, ModuleContext moduleContext) {
|
||||
var newBindings =
|
||||
ir.bindings()
|
||||
.map(
|
||||
binding -> {
|
||||
if (binding instanceof Definition.Type type) {
|
||||
var privateCtorsCnt = type.members().filter(ctor -> ctor.isPrivate()).size();
|
||||
var publicCtorsCnt = type.members().filter(ctor -> !ctor.isPrivate()).size();
|
||||
var ctorsCnt = type.members().size();
|
||||
if (!(privateCtorsCnt == ctorsCnt || publicCtorsCnt == ctorsCnt)) {
|
||||
assert type.location().isDefined();
|
||||
return Syntax.apply(
|
||||
type.location().get(),
|
||||
InconsistentConstructorVisibility$.MODULE$,
|
||||
type.passData(),
|
||||
type.diagnostics());
|
||||
}
|
||||
}
|
||||
return binding;
|
||||
});
|
||||
return ir.copy(
|
||||
ir.imports(),
|
||||
ir.exports(),
|
||||
newBindings,
|
||||
ir.location(),
|
||||
ir.passData(),
|
||||
ir.diagnostics(),
|
||||
ir.id());
|
||||
}
|
||||
|
||||
/** Not supported on a single expression. */
|
||||
@Override
|
||||
public Expression runExpression(Expression ir, InlineContext inlineContext) {
|
||||
return ir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IR> T updateMetadataInDuplicate(T sourceIr, T copyOfIr) {
|
||||
return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr);
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
package org.enso.compiler.pass.analyse;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.enso.compiler.context.InlineContext;
|
||||
import org.enso.compiler.context.ModuleContext;
|
||||
import org.enso.compiler.core.IR;
|
||||
import org.enso.compiler.core.Implicits;
|
||||
import org.enso.compiler.core.ir.Expression;
|
||||
import org.enso.compiler.core.ir.Module;
|
||||
import org.enso.compiler.core.ir.Name;
|
||||
import org.enso.compiler.core.ir.Pattern;
|
||||
import org.enso.compiler.core.ir.expression.Case;
|
||||
import org.enso.compiler.core.ir.expression.Case.Branch;
|
||||
import org.enso.compiler.data.BindingsMap;
|
||||
import org.enso.compiler.data.BindingsMap.ResolvedConstructor;
|
||||
import org.enso.compiler.data.BindingsMap.ResolvedModule;
|
||||
import org.enso.compiler.data.BindingsMap.ResolvedName;
|
||||
import org.enso.compiler.pass.IRPass;
|
||||
import org.enso.compiler.pass.resolve.Patterns$;
|
||||
import org.enso.pkg.QualifiedName;
|
||||
import scala.collection.immutable.Seq;
|
||||
import scala.jdk.javaapi.CollectionConverters;
|
||||
|
||||
/**
|
||||
* Iterates all the symbols in the IR and checks for their {@link
|
||||
* org.enso.compiler.data.BindingsMap.Resolution} metadata. If they are present, it checks if the
|
||||
* symbol is private and if it is used outside of its defining project. If so, a private-access
|
||||
* error IR is generated.
|
||||
*/
|
||||
public class PrivateSymbolsAnalysis implements IRPass {
|
||||
public static final PrivateSymbolsAnalysis INSTANCE = new PrivateSymbolsAnalysis();
|
||||
private UUID uuid;
|
||||
|
||||
private PrivateSymbolsAnalysis() {}
|
||||
|
||||
@Override
|
||||
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
|
||||
this.uuid = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID key() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seq<IRPass> precursorPasses() {
|
||||
List<IRPass> passes =
|
||||
List.of(
|
||||
PrivateModuleAnalysis.INSTANCE, PrivateConstructorAnalysis.INSTANCE, Patterns$.MODULE$);
|
||||
return CollectionConverters.asScala(passes).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seq<IRPass> invalidatedPasses() {
|
||||
return nil();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IR> T updateMetadataInDuplicate(T sourceIr, T copyOfIr) {
|
||||
return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Module runModule(Module ir, ModuleContext moduleContext) {
|
||||
var bindingsMap =
|
||||
(BindingsMap)
|
||||
Implicits.AsMetadata(ir)
|
||||
.unsafeGetMetadata(BindingAnalysis$.MODULE$, () -> "BindingsMap should be present");
|
||||
var newBindings =
|
||||
ir.bindings()
|
||||
.map(binding -> binding.mapExpressions(expr -> processExpression(expr, bindingsMap)));
|
||||
return ir.copy(
|
||||
ir.imports(),
|
||||
ir.exports(),
|
||||
newBindings,
|
||||
ir.location(),
|
||||
ir.passData(),
|
||||
ir.diagnostics(),
|
||||
ir.id());
|
||||
}
|
||||
|
||||
/** Not supported for expressions. */
|
||||
@Override
|
||||
public Expression runExpression(Expression ir, InlineContext inlineContext) {
|
||||
return ir;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Expression processExpression(Expression expr, BindingsMap bindingsMap) {
|
||||
return switch (expr) {
|
||||
case Case.Expr caseExpr -> {
|
||||
var newBranches =
|
||||
caseExpr.branches().map(branch -> processCaseBranch(branch, bindingsMap)).toSeq();
|
||||
var newScrutinee = processExpression(caseExpr.scrutinee(), bindingsMap);
|
||||
yield caseExpr.copy(
|
||||
newScrutinee,
|
||||
newBranches,
|
||||
caseExpr.isNested(),
|
||||
caseExpr.location(),
|
||||
caseExpr.passData(),
|
||||
caseExpr.diagnostics(),
|
||||
caseExpr.id());
|
||||
}
|
||||
case Name name -> processName(name, bindingsMap);
|
||||
default -> expr.mapExpressions(e -> processExpression(e, bindingsMap));
|
||||
};
|
||||
}
|
||||
|
||||
private Branch processCaseBranch(Branch branch, BindingsMap bindingsMap) {
|
||||
var pat = branch.pattern();
|
||||
var newPat = processCasePattern(pat, bindingsMap);
|
||||
var newExpr = processExpression(branch.expression(), bindingsMap);
|
||||
return branch.copy(
|
||||
newPat,
|
||||
newExpr,
|
||||
branch.terminalBranch(),
|
||||
branch.location(),
|
||||
branch.passData(),
|
||||
branch.diagnostics(),
|
||||
branch.id());
|
||||
}
|
||||
|
||||
private Pattern processCasePattern(Pattern pattern, BindingsMap bindingsMap) {
|
||||
if (pattern instanceof Pattern.Constructor cons) {
|
||||
var consName = cons.constructor();
|
||||
var resolvedCons = tryResolveName(consName, bindingsMap);
|
||||
if (resolvedCons != null && isProjectPrivate(resolvedCons)) {
|
||||
var curProjName = getProjName(bindingsMap.currentModule().getName());
|
||||
var resolvedProjName = getProjName(resolvedCons.module().getName());
|
||||
if (!curProjName.equals(resolvedProjName)) {
|
||||
var reason =
|
||||
new org.enso.compiler.core.ir.expression.errors.Pattern.PrivateConstructor(
|
||||
consName.name(), curProjName, resolvedProjName);
|
||||
return new org.enso.compiler.core.ir.expression.errors.Pattern(
|
||||
cons, reason, cons.passData(), cons.diagnostics());
|
||||
}
|
||||
}
|
||||
}
|
||||
return pattern.mapExpressions(e -> processExpression(e, bindingsMap));
|
||||
}
|
||||
|
||||
private Expression processName(Name name, BindingsMap bindingsMap) {
|
||||
var resolvedName = tryResolveName(name, bindingsMap);
|
||||
if (resolvedName != null && isProjectPrivate(resolvedName)) {
|
||||
var curProjName = getProjName(bindingsMap.currentModule().getName());
|
||||
var resolvedProjName = getProjName(resolvedName.module().getName());
|
||||
if (!curProjName.equals(resolvedProjName)) {
|
||||
var reason =
|
||||
new org.enso.compiler.core.ir.expression.errors.Resolution.PrivateEntity(
|
||||
curProjName, resolvedProjName);
|
||||
return new org.enso.compiler.core.ir.expression.errors.Resolution(
|
||||
name, reason, name.passData(), name.diagnostics());
|
||||
}
|
||||
}
|
||||
return name.mapExpressions(e -> processExpression(e, bindingsMap));
|
||||
}
|
||||
|
||||
private static boolean isProjectPrivate(ResolvedName resolvedName) {
|
||||
return switch (resolvedName) {
|
||||
case ResolvedConstructor resolvedCons -> resolvedCons.cons().isProjectPrivate();
|
||||
case ResolvedModule resolvedMod -> {
|
||||
if (resolvedMod.module()
|
||||
instanceof org.enso.compiler.data.BindingsMap$ModuleReference$Concrete concreteMod) {
|
||||
yield concreteMod.module().isPrivate();
|
||||
} else {
|
||||
yield false;
|
||||
}
|
||||
}
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static String getProjName(QualifiedName qualName) {
|
||||
if (qualName.pathAsJava().size() < 2) {
|
||||
return "unknown";
|
||||
} else {
|
||||
return qualName.pathAsJava().get(0) + "." + qualName.pathAsJava().get(1);
|
||||
}
|
||||
}
|
||||
|
||||
private ResolvedName tryResolveName(Name name, BindingsMap bindingsMap) {
|
||||
return switch (name) {
|
||||
case Name.Literal lit -> {
|
||||
var resolved = bindingsMap.resolveName(lit.name());
|
||||
if (resolved.isRight()) {
|
||||
yield (ResolvedName) resolved.getOrElse(() -> null);
|
||||
} else {
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
case Name.Qualified qual -> {
|
||||
var nameParts = qual.parts().map(Name::name);
|
||||
var resolved = bindingsMap.resolveQualifiedName(nameParts);
|
||||
if (resolved.isRight()) {
|
||||
yield (ResolvedName) resolved.getOrElse(() -> null);
|
||||
} else {
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> scala.collection.immutable.List<T> nil() {
|
||||
return (scala.collection.immutable.List<T>) scala.collection.immutable.Nil$.MODULE$;
|
||||
}
|
||||
}
|
@ -50,7 +50,8 @@ class Passes(
|
||||
AmbiguousImportsAnalysis
|
||||
) ++ (if (config.privateCheckEnabled) {
|
||||
List(
|
||||
PrivateModuleAnalysis.INSTANCE
|
||||
PrivateModuleAnalysis.INSTANCE,
|
||||
PrivateConstructorAnalysis.INSTANCE
|
||||
)
|
||||
} else List())
|
||||
++ List(
|
||||
@ -89,7 +90,10 @@ class Passes(
|
||||
DemandAnalysis,
|
||||
AliasAnalysis,
|
||||
TailCall,
|
||||
Patterns,
|
||||
Patterns
|
||||
) ++ (if (config.privateCheckEnabled) {
|
||||
List(PrivateSymbolsAnalysis.INSTANCE)
|
||||
} else List()) ++ List(
|
||||
AliasAnalysis,
|
||||
DataflowAnalysis,
|
||||
CachePreferenceAnalysis,
|
||||
|
@ -717,8 +717,14 @@ object BindingsMap {
|
||||
* @param name the name of the constructor.
|
||||
* @param arity the number of fields in the constructor.
|
||||
* @param allFieldsDefaulted whether all fields provide a default value.
|
||||
* @param isProjectPrivate whether this constructor is project-private.
|
||||
*/
|
||||
case class Cons(name: String, arity: Int, allFieldsDefaulted: Boolean)
|
||||
case class Cons(
|
||||
name: String,
|
||||
arity: Int,
|
||||
allFieldsDefaulted: Boolean,
|
||||
isProjectPrivate: Boolean
|
||||
)
|
||||
|
||||
/** A representation of a sum type
|
||||
*
|
||||
|
@ -63,7 +63,8 @@ case object BindingAnalysis extends IRPass {
|
||||
Cons(
|
||||
m.name.name,
|
||||
m.arguments.length,
|
||||
m.arguments.forall(_.defaultValue.isDefined)
|
||||
m.arguments.forall(_.defaultValue.isDefined),
|
||||
m.isPrivate
|
||||
)
|
||||
),
|
||||
isBuiltinType
|
||||
|
@ -111,7 +111,7 @@ object Patterns extends IRPass {
|
||||
selfTypeResolution.map(Right(_))
|
||||
case _ => None
|
||||
}
|
||||
val resolvedName = resolution
|
||||
val resolvedName: Name = resolution
|
||||
.map {
|
||||
case Left(err) =>
|
||||
val r = errors.Resolution(
|
||||
|
@ -521,6 +521,20 @@ public class ErrorCompilerTest extends CompilerTest {
|
||||
27);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsupportedPrivateModifierInTypeDefinition() throws Exception {
|
||||
var ir = parse("""
|
||||
type T
|
||||
private method self = 42
|
||||
""");
|
||||
assertSingleSyntaxError(
|
||||
ir,
|
||||
Syntax.UnexpectedDeclarationInType$.MODULE$,
|
||||
"Unexpected declaration in the body of a type",
|
||||
9,
|
||||
33);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotation1() throws Exception {
|
||||
var ir = parse("""
|
||||
|
@ -0,0 +1,218 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.AllOf.allOf;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import org.enso.interpreter.util.ScalaConversions;
|
||||
import org.enso.polyglot.PolyglotContext;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.PolyglotException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
public class PrivateAccessTest extends TestBase {
|
||||
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void privateConstructorCanBeCalledInUnknownProject() {
|
||||
var src =
|
||||
"""
|
||||
type My_Type
|
||||
private Cons data
|
||||
main =
|
||||
obj = My_Type.Cons 42
|
||||
obj.data
|
||||
""";
|
||||
try (var ctx = createDefaultContext()) {
|
||||
var res = TestBase.evalModule(ctx, src);
|
||||
assertThat(res.isNumber(), is(true));
|
||||
assertThat(res.asInt(), is(42));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateFieldIsNotExposedToPolyglot() throws IOException {
|
||||
var mainSrc =
|
||||
"""
|
||||
type My_Type
|
||||
private Cons data
|
||||
main = My_Type.Cons 42
|
||||
""";
|
||||
var projDir = createProject("My_Project", mainSrc);
|
||||
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
|
||||
try (var ctx =
|
||||
defaultContextBuilder()
|
||||
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
|
||||
.build()) {
|
||||
var polyCtx = new PolyglotContext(ctx);
|
||||
var mainMod = polyCtx.evalModule(mainSrcPath.toFile());
|
||||
var assocType = mainMod.getAssociatedType();
|
||||
var mainMethod = mainMod.getMethod(assocType, "main").get();
|
||||
var res = mainMethod.execute();
|
||||
assertThat(res.hasMember("data"), is(false));
|
||||
assertThat(res.canInvokeMember("data"), is(false));
|
||||
assertThat(res.getMember("data"), is(nullValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateConstructorIsNotExposedToPolyglot() throws IOException {
|
||||
var mainSrc = """
|
||||
type My_Type
|
||||
private Cons data
|
||||
""";
|
||||
var projDir = createProject("My_Project", mainSrc);
|
||||
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
|
||||
try (var ctx =
|
||||
defaultContextBuilder()
|
||||
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
|
||||
.build()) {
|
||||
var polyCtx = new PolyglotContext(ctx);
|
||||
var mainMod = polyCtx.evalModule(mainSrcPath.toFile());
|
||||
var myType = mainMod.getType("My_Type");
|
||||
assertThat(myType.hasMember("Cons"), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeWithPrivateConstructorExposesPublicMethodsToPolyglot() throws IOException {
|
||||
var mainSrc =
|
||||
"""
|
||||
type My_Type
|
||||
private Cons data
|
||||
get_data self = self.data
|
||||
main =
|
||||
My_Type.Cons 42
|
||||
""";
|
||||
var projDir = createProject("My_Project", mainSrc);
|
||||
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
|
||||
try (var ctx =
|
||||
defaultContextBuilder()
|
||||
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
|
||||
.build()) {
|
||||
var polyCtx = new PolyglotContext(ctx);
|
||||
var mainMod = polyCtx.evalModule(mainSrcPath.toFile());
|
||||
var myType = mainMod.getType("My_Type");
|
||||
var getDataMethod = mainMod.getMethod(myType, "get_data").get();
|
||||
var assocType = mainMod.getAssociatedType();
|
||||
var mainMethod = mainMod.getMethod(assocType, "main").get();
|
||||
var res = mainMethod.execute();
|
||||
assertThat("Atoms should generally have members", res.hasMembers(), is(true));
|
||||
assertThat("data is a private field", res.hasMember("data"), is(false));
|
||||
assertThat("get_data is a public method", res.hasMember("get_data"), is(true));
|
||||
var data = getDataMethod.execute(ScalaConversions.seq(List.of(res)));
|
||||
assertThat("public accessor method can be called from polyglot", data.isNumber(), is(true));
|
||||
assertThat("public accessor method can be called from polyglot", data.asInt(), is(42));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canPatternMatchOnPrivateConstructorFromSameProject() throws IOException {
|
||||
var mainSrc =
|
||||
"""
|
||||
type My_Type
|
||||
private Cons data
|
||||
main =
|
||||
obj = My_Type.Cons 42
|
||||
case obj of
|
||||
My_Type.Cons x -> x
|
||||
_ -> 0
|
||||
""";
|
||||
var projDir = createProject("My_Project", mainSrc);
|
||||
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
|
||||
try (var ctx =
|
||||
defaultContextBuilder()
|
||||
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
|
||||
.build()) {
|
||||
var polyCtx = new PolyglotContext(ctx);
|
||||
var mainMod = polyCtx.evalModule(mainSrcPath.toFile());
|
||||
var assocType = mainMod.getAssociatedType();
|
||||
var mainMethod = mainMod.getMethod(assocType, "main").get();
|
||||
var res = mainMethod.execute();
|
||||
assertThat(res.isNumber(), is(true));
|
||||
assertThat(res.asInt(), is(42));
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests that pattern matching on private constructors fails in compilation. */
|
||||
@Test
|
||||
public void cannotPatternMatchOnPrivateConstructorFromDifferentProject() throws IOException {
|
||||
var libSrc =
|
||||
"""
|
||||
type My_Type
|
||||
private Cons data
|
||||
create x = My_Type.Cons x
|
||||
""";
|
||||
createProject("Lib", libSrc);
|
||||
var projSrc =
|
||||
"""
|
||||
from local.Lib import My_Type
|
||||
main =
|
||||
obj = My_Type.create 42
|
||||
case obj of
|
||||
My_Type.Cons x -> x
|
||||
""";
|
||||
var projDir = createProject("Proj", projSrc);
|
||||
var out = new ByteArrayOutputStream();
|
||||
try (var ctx =
|
||||
defaultContextBuilder()
|
||||
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
|
||||
.option(RuntimeOptions.STRICT_ERRORS, "true")
|
||||
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
|
||||
.out(out)
|
||||
.err(out)
|
||||
.build()) {
|
||||
var polyCtx = new PolyglotContext(ctx);
|
||||
try {
|
||||
polyCtx.getTopScope().compile(true);
|
||||
fail("Expected compiler error");
|
||||
} catch (PolyglotException e) {
|
||||
assertThat(
|
||||
out.toString(),
|
||||
allOf(
|
||||
containsString("error:"),
|
||||
containsString("Project-private constructor"),
|
||||
containsString("cannot be used from")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates temporary project directory structure with a given main source content. No need to
|
||||
* clean it up, as it is managed by JUnit TemporaryFolder rule. Note that we need to create a
|
||||
* project, otherwise the private stuff won't work.
|
||||
*
|
||||
* @param projName Name of the project (as defined in package.yaml).
|
||||
* @param mainSrc Main.enso source content
|
||||
* @return Path to the newly created directly structure - a project directory.
|
||||
*/
|
||||
private Path createProject(String projName, String mainSrc) throws IOException {
|
||||
var projDir = tempFolder.newFolder(projName);
|
||||
assert projDir.exists();
|
||||
var projYaml =
|
||||
"""
|
||||
name: %s
|
||||
version: 0.0.1
|
||||
prefer-local-libraries: true
|
||||
""".formatted(projName);
|
||||
var yamlPath = projDir.toPath().resolve("package.yaml");
|
||||
Files.writeString(yamlPath, projYaml);
|
||||
assert yamlPath.toFile().exists();
|
||||
var srcDir = tempFolder.newFolder(projName, "src");
|
||||
assert srcDir.exists();
|
||||
var mainSrcPath = srcDir.toPath().resolve("Main.enso");
|
||||
Files.writeString(mainSrcPath, mainSrc);
|
||||
assert mainSrcPath.toFile().exists();
|
||||
return projDir.toPath();
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import org.enso.compiler.pass.analyse.{
|
||||
BindingAnalysis,
|
||||
ExportSymbolAnalysis,
|
||||
ImportSymbolAnalysis,
|
||||
PrivateConstructorAnalysis,
|
||||
PrivateModuleAnalysis
|
||||
}
|
||||
import org.enso.compiler.pass.desugar._
|
||||
@ -63,6 +64,7 @@ class PassesTest extends CompilerTest {
|
||||
ImportSymbolAnalysis,
|
||||
AmbiguousImportsAnalysis,
|
||||
PrivateModuleAnalysis.INSTANCE,
|
||||
PrivateConstructorAnalysis.INSTANCE,
|
||||
ExportSymbolAnalysis.INSTANCE,
|
||||
ShadowedPatternFields,
|
||||
UnreachableMatchBranches,
|
||||
|
@ -78,7 +78,7 @@ class BindingAnalysisTest extends CompilerTest {
|
||||
val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.")
|
||||
|
||||
metadata.definedEntities should contain theSameElementsAs List(
|
||||
Type("Foo", List(), List(Cons("Mk_Foo", 3, false)), false),
|
||||
Type("Foo", List(), List(Cons("Mk_Foo", 3, false, false)), false),
|
||||
Type("Bar", List(), List(), false),
|
||||
Type("Baz", List("x", "y"), List(), false),
|
||||
PolyglotSymbol("MyClass"),
|
||||
|
@ -0,0 +1,94 @@
|
||||
package org.enso.compiler.test.pass.analyse
|
||||
|
||||
import org.enso.compiler.Passes
|
||||
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
|
||||
import org.enso.compiler.core.ir.Module
|
||||
import org.enso.compiler.core.ir.expression.errors.Syntax
|
||||
import org.enso.compiler.core.ir.module.scope.Definition
|
||||
import org.enso.compiler.pass.analyse.PrivateConstructorAnalysis
|
||||
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class PrivateModifierTest extends CompilerTest {
|
||||
|
||||
// === Utilities ============================================================
|
||||
|
||||
val passes = new Passes(defaultConfig)
|
||||
|
||||
/** The passes that need to be run before the alias analysis pass. */
|
||||
val precursorPasses: PassGroup =
|
||||
passes.getPrecursors(PrivateConstructorAnalysis.INSTANCE).get
|
||||
|
||||
val passConfig: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(List(precursorPasses), passConfig)
|
||||
|
||||
/** Adds an extension method to run private constructor analysis on an [[Module]].
|
||||
*
|
||||
* @param ir the module to run private constructor analysis on
|
||||
*/
|
||||
implicit class AnalyseModule(ir: Module) {
|
||||
|
||||
/** Runs alias analysis on a module.
|
||||
*
|
||||
* @return [[ir]], with attached aliasing information
|
||||
*/
|
||||
def analyse: Module = {
|
||||
PrivateConstructorAnalysis.INSTANCE.runModule(
|
||||
ir,
|
||||
buildModuleContext(passConfiguration = Some(passConfig))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a defaulted module context.
|
||||
*
|
||||
* @return a defaulted module context
|
||||
*/
|
||||
def mkModuleContext: ModuleContext = {
|
||||
buildModuleContext(freshNameSupply = Some(new FreshNameSupply))
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Private constructor analysis" should {
|
||||
implicit val ctx: ModuleContext = mkModuleContext
|
||||
|
||||
"should accept a single private constructor" in {
|
||||
val ir =
|
||||
"""
|
||||
|type My_Type
|
||||
| private Value a b c
|
||||
|""".stripMargin.preprocessModule.analyse
|
||||
ir.bindings.size shouldBe 1
|
||||
ir.bindings.head.isInstanceOf[Definition.Type] shouldBe true
|
||||
val tp = ir.bindings.head.asInstanceOf[Definition.Type]
|
||||
tp.members.size shouldBe 1
|
||||
tp.members.head
|
||||
}
|
||||
|
||||
"should reject mix of public and private constructors in a type" in {
|
||||
val ir =
|
||||
"""
|
||||
|type My_Type
|
||||
| private Priv_Cons a b
|
||||
| Pub_Cons c d
|
||||
|""".stripMargin.preprocessModule.analyse
|
||||
val errors = ir.preorder().collect { case err: Syntax => err }
|
||||
errors.size shouldBe 1
|
||||
errors.head.reason shouldBe (Syntax.InconsistentConstructorVisibility)
|
||||
}
|
||||
|
||||
"should accept more private constructors in a type" in {
|
||||
val ir =
|
||||
"""
|
||||
|type My_Type
|
||||
| Pub_Cons_1 a b
|
||||
| Pub_Cons_2 c d
|
||||
|""".stripMargin.preprocessModule.analyse
|
||||
val errors = ir.preorder().collect { case err: Syntax => err }
|
||||
errors.isEmpty shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ import com.oracle.truffle.api.TruffleContext;
|
||||
import com.oracle.truffle.api.TruffleLanguage;
|
||||
import com.oracle.truffle.api.TruffleLogger;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
@ -21,7 +20,6 @@ final class EpbContext {
|
||||
private final boolean isInner;
|
||||
private final TruffleLanguage.Env env;
|
||||
private @CompilationFinal TruffleContext innerContext;
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final TruffleLogger log;
|
||||
|
||||
/**
|
||||
|
@ -175,7 +175,7 @@ final class TreeToIr {
|
||||
}
|
||||
if (expr instanceof Private priv) {
|
||||
if (priv.getBody() != null) {
|
||||
var error = translateSyntaxError(priv, new Syntax.UnsupportedSyntax("Private token with body"));
|
||||
var error = translateSyntaxError(priv, new Syntax.UnsupportedSyntax("The `private` keyword is currently not supported for entities other than constructors."));
|
||||
diag = join(error, diag);
|
||||
}
|
||||
if (isPrivate) {
|
||||
@ -338,12 +338,12 @@ final class TreeToIr {
|
||||
return res.reverse();
|
||||
}
|
||||
|
||||
IR translateConstructorDefinition(Tree.ConstructorDefinition cons, Tree inputAst) {
|
||||
IR translateConstructorDefinition(Tree.ConstructorDefinition cons, Tree inputAst, boolean isPrivate) {
|
||||
try {
|
||||
var constructorName = buildName(inputAst, cons.getConstructor());
|
||||
List<DefinitionArgument> args = translateArgumentsDefinition(cons.getArguments());
|
||||
var cAt = getIdentifiedLocation(inputAst);
|
||||
return new Definition.Data(constructorName, args, nil(), cAt, meta(), diag());
|
||||
return new Definition.Data(constructorName, args, nil(), cAt, isPrivate, meta(), diag());
|
||||
} catch (SyntaxException ex) {
|
||||
return ex.toError();
|
||||
}
|
||||
@ -369,7 +369,17 @@ final class TreeToIr {
|
||||
return switch (inputAst) {
|
||||
case null -> appendTo;
|
||||
|
||||
case Tree.ConstructorDefinition cons -> join(translateConstructorDefinition(cons, inputAst), appendTo);
|
||||
case Tree.Private priv -> {
|
||||
if (priv.getBody() instanceof Tree.ConstructorDefinition consDef) {
|
||||
var translated = translateConstructorDefinition(consDef, priv, true);
|
||||
yield join(translated, appendTo);
|
||||
} else {
|
||||
var errorIr = translateSyntaxError(priv, Syntax.UnexpectedDeclarationInType$.MODULE$);
|
||||
yield join(errorIr, appendTo);
|
||||
}
|
||||
}
|
||||
|
||||
case Tree.ConstructorDefinition cons -> join(translateConstructorDefinition(cons, inputAst, false), appendTo);
|
||||
|
||||
case Tree.TypeDef def -> {
|
||||
var ir = translateSyntaxError(def, Syntax.UnexpectedDeclarationInType$.MODULE$);
|
||||
|
@ -113,4 +113,18 @@ object Pattern {
|
||||
s"Wrong number of fields when matching on $consName." +
|
||||
s" Expected $expected fields, but provided $actual"
|
||||
}
|
||||
|
||||
/** An error when a project-private constructor is used in the pattern.
|
||||
* @param consName Name of the constructor. Does not have to be fully qualified.
|
||||
* @param callerProject The project name of the caller.
|
||||
* @param calleeProject The project name of the callee. The constructor is in this project.
|
||||
*/
|
||||
case class PrivateConstructor(
|
||||
consName: String,
|
||||
callerProject: String,
|
||||
calleeProject: String
|
||||
) extends Reason {
|
||||
override def explain: String =
|
||||
s"Project-private constructor '$consName' in project '$calleeProject' cannot be used from project '$callerProject'"
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +191,18 @@ object Resolution {
|
||||
s"Variable `${originalName.name}` is not defined"
|
||||
}
|
||||
|
||||
/** An error when a project-private entity (module, type, method) is used from a different project.
|
||||
* @param callerProject Name of the project of caller.
|
||||
* @param calleeProject Name of the project of callee.
|
||||
*/
|
||||
case class PrivateEntity(
|
||||
callerProject: String,
|
||||
calleeProject: String
|
||||
) extends Reason {
|
||||
override def explain(originalName: Name): String =
|
||||
s"Project-private entity '${originalName.name}' in project '$calleeProject' cannot be used from project '$callerProject'"
|
||||
}
|
||||
|
||||
/** An error coming from name resolver.
|
||||
*
|
||||
* @param err the original error.
|
||||
|
@ -147,6 +147,11 @@ object Syntax {
|
||||
s"Syntax is not supported yet: $syntaxName"
|
||||
}
|
||||
|
||||
case object InconsistentConstructorVisibility extends Reason {
|
||||
override def explanation: String =
|
||||
"Private and public constructors cannot be mixed within a single type"
|
||||
}
|
||||
|
||||
case object InvalidUnderscore extends Reason {
|
||||
override def explanation: String =
|
||||
s"Invalid use of _"
|
||||
|
@ -155,6 +155,7 @@ object Definition {
|
||||
* @param arguments the arguments to the atom constructor
|
||||
* @param annotations the list of annotations
|
||||
* @param location the source location that the node corresponds to
|
||||
* @param isPrivate If the constructor is private (project-private).
|
||||
* @param passData the pass metadata associated with this node
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
@ -163,6 +164,7 @@ object Definition {
|
||||
arguments: List[DefinitionArgument],
|
||||
annotations: List[Name.GenericAnnotation],
|
||||
location: Option[IdentifiedLocation],
|
||||
isPrivate: Boolean = false,
|
||||
passData: MetadataStorage = new MetadataStorage(),
|
||||
diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends IR
|
||||
@ -194,6 +196,7 @@ object Definition {
|
||||
arguments,
|
||||
annotations,
|
||||
location,
|
||||
isPrivate,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
|
@ -1400,6 +1400,24 @@ public class EnsoParserTest {
|
||||
equivalenceTest("private", "\n\nprivate");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrivateConstructor() throws Exception {
|
||||
parseTest(
|
||||
"""
|
||||
type My_Type
|
||||
private Ctor_1 a b
|
||||
private Ctor_2 c d
|
||||
""");
|
||||
// Mixing public and private constructor is a semantic error, not a syntax error.
|
||||
// So parsing should be fine.
|
||||
parseTest(
|
||||
"""
|
||||
type My_Type
|
||||
private Ctor_1 a b
|
||||
Ctor_2 c d
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ise_184219679() throws IOException {
|
||||
parseTest(
|
||||
|
@ -94,6 +94,7 @@ final class SuggestionBuilder[A: IndexedSource](
|
||||
annotations,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_
|
||||
) =>
|
||||
buildAtomConstructor(
|
||||
|
@ -29,9 +29,9 @@ public abstract class EnsoRootNode extends RootNode {
|
||||
*
|
||||
* @param language the language instance in which this will execute
|
||||
* @param localScope a reference to the construct local scope
|
||||
* @param moduleScope a reference to the construct module scope
|
||||
* @param moduleScope a reference to the construct module scope. May be {@code null}.
|
||||
* @param name the name of the construct
|
||||
* @param sourceSection a reference to the source code being executed
|
||||
* @param sourceSection a reference to the source code being executed. May be {@code null}.
|
||||
*/
|
||||
protected EnsoRootNode(
|
||||
EnsoLanguage language,
|
||||
@ -41,6 +41,7 @@ public abstract class EnsoRootNode extends RootNode {
|
||||
SourceSection sourceSection) {
|
||||
super(language, buildFrameDescriptor(localScope));
|
||||
Objects.requireNonNull(language);
|
||||
Objects.requireNonNull(localScope);
|
||||
this.name = name;
|
||||
this.localScope = localScope;
|
||||
this.moduleScope = moduleScope;
|
||||
|
@ -30,13 +30,11 @@ import org.enso.interpreter.node.expression.builtin.meta.AtomWithAHoleNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.IsValueOfTypeNode;
|
||||
import org.enso.interpreter.node.expression.literal.LiteralNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.Annotation;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConstructor;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition.ExecutionMode;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgument;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
|
||||
import org.enso.interpreter.runtime.data.EnsoMultiValue;
|
||||
@ -479,14 +477,11 @@ public abstract class ReadArgumentCheckNode extends Node {
|
||||
@Child private ReadArgumentCheckNode check;
|
||||
|
||||
static final FunctionSchema SCHEMA =
|
||||
new FunctionSchema(
|
||||
FunctionSchema.CallerFrameAccess.NONE,
|
||||
new ArgumentDefinition[] {
|
||||
new ArgumentDefinition(0, "delegate", null, null, ExecutionMode.EXECUTE)
|
||||
},
|
||||
new boolean[] {true},
|
||||
new CallArgumentInfo[0],
|
||||
new Annotation[0]);
|
||||
FunctionSchema.newBuilder()
|
||||
.argumentDefinitions(
|
||||
new ArgumentDefinition(0, "delegate", null, null, ExecutionMode.EXECUTE))
|
||||
.hasPreapplied(true)
|
||||
.build();
|
||||
|
||||
LazyCheckRootNode(TruffleLanguage<?> language, ReadArgumentCheckNode check) {
|
||||
super(language);
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.enso.interpreter.node.callable.dispatch;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.TruffleFile;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.ImportStatic;
|
||||
import com.oracle.truffle.api.dsl.NeverDefault;
|
||||
@ -12,6 +14,8 @@ import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.UUID;
|
||||
import org.enso.interpreter.Constants;
|
||||
import org.enso.interpreter.node.BaseNode;
|
||||
import org.enso.interpreter.node.EnsoRootNode;
|
||||
import org.enso.interpreter.node.MethodRootNode;
|
||||
import org.enso.interpreter.node.callable.CaptureCallerInfoNode;
|
||||
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode;
|
||||
@ -22,7 +26,11 @@ import org.enso.interpreter.runtime.callable.CallerInfo;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
|
||||
import org.enso.interpreter.runtime.data.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
import org.enso.pkg.Package;
|
||||
|
||||
/**
|
||||
* This class represents the protocol for remapping the arguments provided at a call site into the
|
||||
@ -86,6 +94,25 @@ public abstract class InvokeFunctionNode extends BaseNode {
|
||||
return EnsoContext.get(this);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private PanicException makePrivateAccessPanic(Function targetFunction) {
|
||||
String thisProjName = null;
|
||||
if (getThisProject() != null) {
|
||||
thisProjName = getThisProject().libraryName().qualifiedName();
|
||||
}
|
||||
String targetProjName = null;
|
||||
if (getFunctionProject(targetFunction) != null) {
|
||||
targetProjName = getFunctionProject(targetFunction).libraryName().qualifiedName();
|
||||
}
|
||||
var funcName = targetFunction.getName();
|
||||
var err =
|
||||
EnsoContext.get(this)
|
||||
.getBuiltins()
|
||||
.error()
|
||||
.makePrivateAccessError(thisProjName, targetProjName, funcName);
|
||||
return new PanicException(err, this);
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"!getContext().isInlineCachingDisabled()", "function.getSchema() == cachedSchema"},
|
||||
limit = Constants.CacheSizes.ARGUMENT_SORTER_NODE)
|
||||
@ -103,6 +130,10 @@ public abstract class InvokeFunctionNode extends BaseNode {
|
||||
"build(argumentMapping, getDefaultsExecutionMode(), getArgumentsExecutionMode(),"
|
||||
+ " getTailStatus())")
|
||||
CurryNode curryNode) {
|
||||
if (cachedSchema.isProjectPrivate() && !isInSameProject(function)) {
|
||||
throw makePrivateAccessPanic(function);
|
||||
}
|
||||
|
||||
ArgumentSorterNode.MappedArguments mappedArguments =
|
||||
mappingNode.execute(callerFrame, function, state, arguments);
|
||||
CallerInfo callerInfo = null;
|
||||
@ -143,6 +174,10 @@ public abstract class InvokeFunctionNode extends BaseNode {
|
||||
Object[] arguments,
|
||||
@Cached IndirectArgumentSorterNode mappingNode,
|
||||
@Cached IndirectCurryNode curryNode) {
|
||||
if (function.getSchema().isProjectPrivate() && !isInSameProject(function)) {
|
||||
throw makePrivateAccessPanic(function);
|
||||
}
|
||||
|
||||
CallArgumentInfo.ArgumentMapping argumentMapping =
|
||||
CallArgumentInfo.ArgumentMappingBuilder.generate(function.getSchema(), getSchema());
|
||||
|
||||
@ -224,4 +259,44 @@ public abstract class InvokeFunctionNode extends BaseNode {
|
||||
public UUID getId() {
|
||||
return functionCallInstrumentationNode.getId();
|
||||
}
|
||||
|
||||
/** Returns true if the given function is in the same project as this node. */
|
||||
private boolean isInSameProject(Function function) {
|
||||
var thisProj = getThisProject();
|
||||
var funcProj = getFunctionProject(function);
|
||||
return thisProj == funcProj;
|
||||
}
|
||||
|
||||
private Package<TruffleFile> getThisProject() {
|
||||
if (getRootNode() instanceof EnsoRootNode thisRootNode) {
|
||||
var modScope = thisRootNode.getModuleScope();
|
||||
if (modScope != null) {
|
||||
return modScope.getModule().getPackage();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Package<TruffleFile> getFunctionProject(Function function) {
|
||||
var modScope = getModuleScopeForFunction(function);
|
||||
if (modScope != null) {
|
||||
return modScope.getModule().getPackage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ModuleScope getModuleScopeForFunction(Function function) {
|
||||
var cons = AtomConstructor.accessorFor(function);
|
||||
if (cons != null) {
|
||||
return cons.getDefinitionScope();
|
||||
}
|
||||
cons = MethodRootNode.constructorFor(function);
|
||||
if (cons != null) {
|
||||
return cons.getDefinitionScope();
|
||||
}
|
||||
if (function.getCallTarget().getRootNode() instanceof EnsoRootNode ensoRootNode) {
|
||||
return ensoRootNode.getModuleScope();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ public class CreateFunctionNode extends ExpressionNode {
|
||||
|
||||
private CreateFunctionNode(RootCallTarget callTarget, ArgumentDefinition[] args) {
|
||||
this.callTarget = callTarget;
|
||||
this.schema = new FunctionSchema(args);
|
||||
this.schema = FunctionSchema.newBuilder().argumentDefinitions(args).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,8 +60,8 @@ public abstract class Builtin {
|
||||
}
|
||||
type =
|
||||
containsValues()
|
||||
? Type.create(name, scope, supertype, builtins.get(Any.class).getType(), true)
|
||||
: Type.createSingleton(name, scope, supertype, true);
|
||||
? Type.create(name, scope, supertype, builtins.get(Any.class).getType(), true, false)
|
||||
: Type.createSingleton(name, scope, supertype, true, false);
|
||||
}
|
||||
if (constructors == null) {
|
||||
var conses = getDeclaredConstructors();
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.enso.interpreter.node.expression.builtin.error;
|
||||
|
||||
import java.util.List;
|
||||
import org.enso.interpreter.dsl.BuiltinType;
|
||||
import org.enso.interpreter.node.expression.builtin.UniquelyConstructibleBuiltin;
|
||||
|
||||
@BuiltinType
|
||||
public class PrivateAccess extends UniquelyConstructibleBuiltin {
|
||||
|
||||
@Override
|
||||
protected String getConstructorName() {
|
||||
return "Error";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getConstructorParamNames() {
|
||||
return List.of("this_project_name", "target_project_name", "target_method_name");
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode;
|
||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.Annotation;
|
||||
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
@ -154,17 +153,14 @@ public abstract class AtomWithAHoleNode extends Node {
|
||||
private SwapAtomFieldNode() {
|
||||
super(null);
|
||||
this.schema =
|
||||
new FunctionSchema(
|
||||
FunctionSchema.CallerFrameAccess.NONE,
|
||||
new ArgumentDefinition[] {
|
||||
new ArgumentDefinition(
|
||||
0, "lazy", null, null, ArgumentDefinition.ExecutionMode.EXECUTE),
|
||||
new ArgumentDefinition(
|
||||
1, "value", null, null, ArgumentDefinition.ExecutionMode.EXECUTE)
|
||||
},
|
||||
new boolean[] {true, false},
|
||||
new CallArgumentInfo[0],
|
||||
new Annotation[0]);
|
||||
FunctionSchema.newBuilder()
|
||||
.argumentDefinitions(
|
||||
new ArgumentDefinition(
|
||||
0, "lazy", null, null, ArgumentDefinition.ExecutionMode.EXECUTE),
|
||||
new ArgumentDefinition(
|
||||
1, "value", null, null, ArgumentDefinition.ExecutionMode.EXECUTE))
|
||||
.hasPreapplied(true, false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NeverDefault
|
||||
|
@ -9,24 +9,21 @@ import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.Annotation;
|
||||
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
|
||||
import org.enso.polyglot.debugger.IdExecutionService;
|
||||
|
||||
final class InstrumentorEvalNode extends RootNode {
|
||||
private static final FunctionSchema SUSPENDED_EVAL =
|
||||
new FunctionSchema(
|
||||
FunctionSchema.CallerFrameAccess.NONE,
|
||||
new ArgumentDefinition[] {
|
||||
new ArgumentDefinition(0, "expr", null, null, ArgumentDefinition.ExecutionMode.EXECUTE),
|
||||
new ArgumentDefinition(1, "info", null, null, ArgumentDefinition.ExecutionMode.EXECUTE)
|
||||
},
|
||||
new boolean[] {true, true},
|
||||
new CallArgumentInfo[0],
|
||||
new Annotation[0]);
|
||||
FunctionSchema.newBuilder()
|
||||
.argumentDefinitions(
|
||||
new ArgumentDefinition(
|
||||
0, "expr", null, null, ArgumentDefinition.ExecutionMode.EXECUTE),
|
||||
new ArgumentDefinition(
|
||||
1, "info", null, null, ArgumentDefinition.ExecutionMode.EXECUTE))
|
||||
.hasPreapplied(true, true)
|
||||
.build();
|
||||
private static Reference<InstrumentorEvalNode> last = new WeakReference<>(null);
|
||||
|
||||
private InstrumentorEvalNode(EnsoLanguage language) {
|
||||
|
@ -72,10 +72,10 @@ public class LiteralNode extends ExpressionNode implements Patchable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the constant value of this string literal.
|
||||
* Returns the constant value of this literal.
|
||||
*
|
||||
* @param frame the stack frame for execution
|
||||
* @return the string value this node was created with
|
||||
* @return the value this node was created with
|
||||
*/
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
|
@ -24,6 +24,7 @@ import org.enso.interpreter.node.expression.builtin.error.NoSuchMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.error.NotInvokable;
|
||||
import org.enso.interpreter.node.expression.builtin.error.NumberParseError;
|
||||
import org.enso.interpreter.node.expression.builtin.error.Panic;
|
||||
import org.enso.interpreter.node.expression.builtin.error.PrivateAccess;
|
||||
import org.enso.interpreter.node.expression.builtin.error.SyntaxError;
|
||||
import org.enso.interpreter.node.expression.builtin.error.TypeError;
|
||||
import org.enso.interpreter.node.expression.builtin.error.Unimplemented;
|
||||
@ -32,6 +33,7 @@ import org.enso.interpreter.node.expression.builtin.error.UnsupportedArgumentTyp
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.data.EnsoObject;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.atom.Atom;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
@ -58,6 +60,7 @@ public final class Error {
|
||||
private final UnsupportedArgumentTypes unsupportedArgumentsError;
|
||||
private final ModuleDoesNotExist moduleDoesNotExistError;
|
||||
private final NotInvokable notInvokable;
|
||||
private final PrivateAccess privateAccessError;
|
||||
private final InvalidConversionTarget invalidConversionTarget;
|
||||
private final NoSuchField noSuchField;
|
||||
private final NumberParseError numberParseError;
|
||||
@ -96,6 +99,7 @@ public final class Error {
|
||||
unsupportedArgumentsError = builtins.getBuiltinType(UnsupportedArgumentTypes.class);
|
||||
moduleDoesNotExistError = builtins.getBuiltinType(ModuleDoesNotExist.class);
|
||||
notInvokable = builtins.getBuiltinType(NotInvokable.class);
|
||||
privateAccessError = builtins.getBuiltinType(PrivateAccess.class);
|
||||
invalidConversionTarget = builtins.getBuiltinType(InvalidConversionTarget.class);
|
||||
noSuchField = builtins.getBuiltinType(NoSuchField.class);
|
||||
numberParseError = builtins.getBuiltinType(NumberParseError.class);
|
||||
@ -310,6 +314,22 @@ public final class Error {
|
||||
return notInvokable.newInstance(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param thisProjectName Current project name. May be null.
|
||||
* @param targetProjectName Target method project name. May be null.
|
||||
* @param targetMethodName Name of the method that is project-private and cannot be accessed.
|
||||
*/
|
||||
public Atom makePrivateAccessError(
|
||||
String thisProjectName, String targetProjectName, String targetMethodName) {
|
||||
assert targetMethodName != null;
|
||||
EnsoObject thisProjName =
|
||||
thisProjectName != null ? Text.create(thisProjectName) : context.getNothing();
|
||||
EnsoObject targetProjName =
|
||||
targetProjectName != null ? Text.create(targetProjectName) : context.getNothing();
|
||||
return privateAccessError.newInstance(
|
||||
thisProjName, targetProjName, Text.create(targetMethodName));
|
||||
}
|
||||
|
||||
public ForbiddenOperation getForbiddenOperation() {
|
||||
return forbiddenOperation;
|
||||
}
|
||||
@ -324,7 +344,7 @@ public final class Error {
|
||||
|
||||
/**
|
||||
* @param index the position at which the original error occured
|
||||
* @param inner_error the original error
|
||||
* @param innerError the original error
|
||||
* @return an error indicating the index of the error
|
||||
*/
|
||||
public Atom makeMapError(long index, Object innerError) {
|
||||
|
@ -218,12 +218,13 @@ public final class CallArgumentInfo {
|
||||
this.existingOversaturatedArgs.length,
|
||||
newOversaturatedArgInfo.length);
|
||||
|
||||
return new FunctionSchema(
|
||||
originalSchema.getCallerFrameAccess(),
|
||||
definitions,
|
||||
argumentUsed,
|
||||
oversaturatedArgInfo,
|
||||
originalSchema.getAnnotations());
|
||||
return FunctionSchema.newBuilder()
|
||||
.callerFrameAccess(originalSchema.getCallerFrameAccess())
|
||||
.argumentDefinitions(definitions)
|
||||
.hasPreapplied(argumentUsed)
|
||||
.oversaturatedArguments(oversaturatedArgInfo)
|
||||
.annotations(originalSchema.getAnnotations())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,9 @@ import org.enso.interpreter.node.callable.InteropApplicationNode;
|
||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.Annotation;
|
||||
import org.enso.interpreter.runtime.callable.CallerInfo;
|
||||
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||
import org.enso.interpreter.runtime.callable.function.FunctionSchema.CallerFrameAccess;
|
||||
import org.enso.interpreter.runtime.data.EnsoObject;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers;
|
||||
@ -93,7 +93,7 @@ public final class Function implements EnsoObject {
|
||||
*/
|
||||
public static Function fromBuiltinRootNode(BuiltinRootNode node, ArgumentDefinition... args) {
|
||||
RootCallTarget callTarget = node.getCallTarget();
|
||||
FunctionSchema schema = new FunctionSchema(args);
|
||||
FunctionSchema schema = FunctionSchema.newBuilder().argumentDefinitions(args).build();
|
||||
return new Function(callTarget, null, schema);
|
||||
}
|
||||
|
||||
@ -111,7 +111,10 @@ public final class Function implements EnsoObject {
|
||||
BuiltinRootNode node, ArgumentDefinition... args) {
|
||||
RootCallTarget callTarget = node.getCallTarget();
|
||||
FunctionSchema schema =
|
||||
new FunctionSchema(FunctionSchema.CallerFrameAccess.FULL, new Annotation[0], args);
|
||||
FunctionSchema.newBuilder()
|
||||
.argumentDefinitions(args)
|
||||
.callerFrameAccess(CallerFrameAccess.FULL)
|
||||
.build();
|
||||
return new Function(callTarget, null, schema);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,8 @@ public final class FunctionSchema {
|
||||
}
|
||||
}
|
||||
|
||||
public static final FunctionSchema THUNK = new FunctionSchema();
|
||||
public static final FunctionSchema THUNK =
|
||||
FunctionSchema.newBuilder().callerFrameAccess(CallerFrameAccess.NONE).build();
|
||||
|
||||
private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos;
|
||||
private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied;
|
||||
@ -41,6 +42,11 @@ public final class FunctionSchema {
|
||||
private final boolean hasOversaturatedArguments;
|
||||
private final CallerFrameAccess callerFrameAccess;
|
||||
private final boolean isFullyApplied;
|
||||
private final boolean isProjectPrivate;
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link FunctionSchema} instance.
|
||||
@ -54,17 +60,19 @@ public final class FunctionSchema {
|
||||
* this function so far.
|
||||
* @param annotations the list of annotations defined on this function.
|
||||
*/
|
||||
public FunctionSchema(
|
||||
private FunctionSchema(
|
||||
CallerFrameAccess callerFrameAccess,
|
||||
ArgumentDefinition[] argumentInfos,
|
||||
boolean[] hasPreApplied,
|
||||
CallArgumentInfo[] oversaturatedArguments,
|
||||
Annotation[] annotations) {
|
||||
Annotation[] annotations,
|
||||
boolean isProjectPrivate) {
|
||||
this.argumentInfos = argumentInfos;
|
||||
this.oversaturatedArguments = oversaturatedArguments;
|
||||
this.hasPreApplied = hasPreApplied;
|
||||
this.callerFrameAccess = callerFrameAccess;
|
||||
this.annotations = annotations;
|
||||
this.isProjectPrivate = isProjectPrivate;
|
||||
boolean hasAnyPreApplied = false;
|
||||
for (boolean b : hasPreApplied) {
|
||||
if (b) {
|
||||
@ -78,51 +86,6 @@ public final class FunctionSchema {
|
||||
this.isFullyApplied = isFullyApplied(InvokeCallableNode.DefaultsExecutionMode.EXECUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link FunctionSchema} instance assuming the function has no partially applied
|
||||
* arguments.
|
||||
*
|
||||
* @param callerFrameAccess the declaration of need to access the caller frame from the function.
|
||||
* @param argumentInfos Definition site arguments information.
|
||||
* @param annotations the list of annotations defined on this function.
|
||||
*/
|
||||
public FunctionSchema(
|
||||
CallerFrameAccess callerFrameAccess,
|
||||
Annotation[] annotations,
|
||||
ArgumentDefinition... argumentInfos) {
|
||||
this(
|
||||
callerFrameAccess,
|
||||
argumentInfos,
|
||||
new boolean[argumentInfos.length],
|
||||
new CallArgumentInfo[0],
|
||||
annotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link FunctionSchema} instance assuming the function has no partially applied
|
||||
* arguments.
|
||||
*
|
||||
* <p>Caller frame access is assumed to be {@link CallerFrameAccess#NONE}.
|
||||
*
|
||||
* @param annotations the list of annotations defined on this function.
|
||||
* @param argumentInfos Definition site arguments information.
|
||||
*/
|
||||
public FunctionSchema(Annotation[] annotations, ArgumentDefinition... argumentInfos) {
|
||||
this(CallerFrameAccess.NONE, annotations, argumentInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link FunctionSchema} instance assuming the function has no annotations or
|
||||
* partially applied arguments.
|
||||
*
|
||||
* <p>Caller frame access is assumed to be {@link CallerFrameAccess#NONE}.
|
||||
*
|
||||
* @param argumentInfos Definition site arguments information
|
||||
*/
|
||||
public FunctionSchema(ArgumentDefinition... argumentInfos) {
|
||||
this(CallerFrameAccess.NONE, new Annotation[0], argumentInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this function have a partially applied argument at a given position?
|
||||
*
|
||||
@ -154,6 +117,10 @@ public final class FunctionSchema {
|
||||
return this.hasOversaturatedArguments;
|
||||
}
|
||||
|
||||
public boolean isProjectPrivate() {
|
||||
return isProjectPrivate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the definition site arguments information.
|
||||
*
|
||||
@ -264,4 +231,77 @@ public final class FunctionSchema {
|
||||
public boolean isFullyApplied() {
|
||||
return isFullyApplied;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private boolean isProjectPrivate = false;
|
||||
private Annotation[] annotations = new Annotation[0];
|
||||
private ArgumentDefinition[] argDefs = new ArgumentDefinition[0];
|
||||
private CallArgumentInfo[] callArgInfos = new CallArgumentInfo[0];
|
||||
private CallerFrameAccess callerFrameAccess = CallerFrameAccess.NONE;
|
||||
private boolean[] hasPreapplied = new boolean[0];
|
||||
|
||||
private Builder() {}
|
||||
|
||||
/** Set the function to be project private. */
|
||||
public Builder projectPrivate() {
|
||||
this.isProjectPrivate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param annotations the list of annotations defined on this function.
|
||||
*/
|
||||
public Builder annotations(Annotation... annotations) {
|
||||
this.annotations = annotations;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param argDefs Definition site arguments information.
|
||||
*/
|
||||
public Builder argumentDefinitions(ArgumentDefinition... argDefs) {
|
||||
if (hasPreapplied.length > argDefs.length) {
|
||||
throw new AssertionError("hasPreapplied must not be specified before argument definitions");
|
||||
}
|
||||
this.argDefs = argDefs;
|
||||
this.hasPreapplied = new boolean[argDefs.length];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callArgumentInfos information about any unused, oversaturated arguments passed to this
|
||||
* function so far.
|
||||
*/
|
||||
public Builder oversaturatedArguments(CallArgumentInfo... callArgumentInfos) {
|
||||
this.callArgInfos = callArgumentInfos;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callerFrameAccess the declaration of whether access to caller frame is required for
|
||||
* this function.
|
||||
*/
|
||||
public Builder callerFrameAccess(CallerFrameAccess callerFrameAccess) {
|
||||
this.callerFrameAccess = callerFrameAccess;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hasPreapplied A flags collection such that {@code hasPreApplied[i]} is true iff a
|
||||
* function has a partially applied argument at position {@code i}.
|
||||
*/
|
||||
public Builder hasPreapplied(boolean... hasPreapplied) {
|
||||
if (hasPreapplied.length != argDefs.length) {
|
||||
throw new AssertionError("Argument definitions must be specified before hasPreapplied");
|
||||
}
|
||||
this.hasPreapplied = hasPreapplied;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FunctionSchema build() {
|
||||
assert argDefs.length == hasPreapplied.length;
|
||||
return new FunctionSchema(
|
||||
callerFrameAccess, argDefs, hasPreapplied, callArgInfos, annotations, isProjectPrivate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,47 +34,65 @@ public final class Type implements EnsoObject {
|
||||
private final Type supertype;
|
||||
private final Type eigentype;
|
||||
private final Map<String, AtomConstructor> constructors;
|
||||
private final boolean isProjectPrivate;
|
||||
|
||||
private boolean gettersGenerated;
|
||||
|
||||
private Type(
|
||||
String name, ModuleScope definitionScope, Type supertype, Type eigentype, boolean builtin) {
|
||||
String name,
|
||||
ModuleScope definitionScope,
|
||||
Type supertype,
|
||||
Type eigentype,
|
||||
boolean builtin,
|
||||
boolean isProjectPrivate) {
|
||||
this.name = name;
|
||||
this.definitionScope = definitionScope;
|
||||
this.supertype = supertype;
|
||||
this.builtin = builtin;
|
||||
this.isProjectPrivate = isProjectPrivate;
|
||||
this.eigentype = Objects.requireNonNullElse(eigentype, this);
|
||||
this.constructors = new HashMap<>();
|
||||
}
|
||||
|
||||
public static Type createSingleton(
|
||||
String name, ModuleScope definitionScope, Type supertype, boolean builtin) {
|
||||
var result = new Type(name, definitionScope, supertype, null, builtin);
|
||||
String name,
|
||||
ModuleScope definitionScope,
|
||||
Type supertype,
|
||||
boolean builtin,
|
||||
boolean isProjectPrivate) {
|
||||
var result = new Type(name, definitionScope, supertype, null, builtin, isProjectPrivate);
|
||||
result.generateQualifiedAccessor();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Type create(
|
||||
String name, ModuleScope definitionScope, Type supertype, Type any, boolean builtin) {
|
||||
var eigentype = new Type(name + ".type", definitionScope, any, null, builtin);
|
||||
var result = new Type(name, definitionScope, supertype, eigentype, builtin);
|
||||
String name,
|
||||
ModuleScope definitionScope,
|
||||
Type supertype,
|
||||
Type any,
|
||||
boolean builtin,
|
||||
boolean isProjectPrivate) {
|
||||
var eigentype = new Type(name + ".type", definitionScope, any, null, builtin, isProjectPrivate);
|
||||
var result = new Type(name, definitionScope, supertype, eigentype, builtin, isProjectPrivate);
|
||||
result.generateQualifiedAccessor();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Type noType() {
|
||||
return new Type("null", null, null, null, false);
|
||||
return new Type("null", null, null, null, false, false);
|
||||
}
|
||||
|
||||
private void generateQualifiedAccessor() {
|
||||
var node = new ConstantNode(null, this);
|
||||
var function =
|
||||
new Function(
|
||||
node.getCallTarget(),
|
||||
null,
|
||||
new FunctionSchema(
|
||||
var schemaBldr =
|
||||
FunctionSchema.newBuilder()
|
||||
.argumentDefinitions(
|
||||
new ArgumentDefinition(
|
||||
0, "this", null, null, ArgumentDefinition.ExecutionMode.EXECUTE)));
|
||||
0, "this", null, null, ArgumentDefinition.ExecutionMode.EXECUTE));
|
||||
if (isProjectPrivate) {
|
||||
schemaBldr.projectPrivate();
|
||||
}
|
||||
var function = new Function(node.getCallTarget(), null, schemaBldr.build());
|
||||
definitionScope.registerMethod(definitionScope.getAssociatedType(), this.name, function);
|
||||
}
|
||||
|
||||
@ -118,6 +136,18 @@ public final class Type implements EnsoObject {
|
||||
return builtin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff this type is project-private. A type is project-private iff all its
|
||||
* constructors are project-private. Note that during the compilation, it is ensured by the {@link
|
||||
* org.enso.compiler.pass.analyse.PrivateConstructorAnalysis} compiler pass that all the
|
||||
* constructors are either public or project-private.
|
||||
*
|
||||
* @return true iff this type is project-private.
|
||||
*/
|
||||
public boolean isProjectPrivate() {
|
||||
return isProjectPrivate;
|
||||
}
|
||||
|
||||
private Type getSupertype() {
|
||||
if (supertype == null) {
|
||||
if (builtin) {
|
||||
@ -152,17 +182,20 @@ public final class Type implements EnsoObject {
|
||||
var roots = AtomConstructor.collectFieldAccessors(language, this);
|
||||
roots.forEach(
|
||||
(name, node) -> {
|
||||
var f =
|
||||
new Function(
|
||||
node.getCallTarget(),
|
||||
null,
|
||||
new FunctionSchema(
|
||||
var schemaBldr =
|
||||
FunctionSchema.newBuilder()
|
||||
.argumentDefinitions(
|
||||
new ArgumentDefinition(
|
||||
0,
|
||||
Constants.Names.SELF_ARGUMENT,
|
||||
null,
|
||||
null,
|
||||
ArgumentDefinition.ExecutionMode.EXECUTE)));
|
||||
ArgumentDefinition.ExecutionMode.EXECUTE));
|
||||
if (isProjectPrivate) {
|
||||
schemaBldr.projectPrivate();
|
||||
}
|
||||
var funcSchema = schemaBldr.build();
|
||||
var f = new Function(node.getCallTarget(), null, funcSchema);
|
||||
definitionScope.registerMethod(this, name, f);
|
||||
});
|
||||
}
|
||||
@ -270,18 +303,29 @@ public final class Type implements EnsoObject {
|
||||
@ExportMessage
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
EnsoObject getMembers(boolean includeInternal) {
|
||||
return ArrayLikeHelpers.wrapStrings(constructors.keySet().toArray(String[]::new));
|
||||
if (isProjectPrivate) {
|
||||
return ArrayLikeHelpers.empty();
|
||||
} else {
|
||||
return ArrayLikeHelpers.wrapStrings(constructors.keySet().toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
boolean isMemberReadable(String member) {
|
||||
return constructors.containsKey(member);
|
||||
if (isProjectPrivate) {
|
||||
return false;
|
||||
} else {
|
||||
return constructors.containsKey(member);
|
||||
}
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
Object readMember(String member) throws UnknownIdentifierException {
|
||||
if (isProjectPrivate) {
|
||||
throw UnknownIdentifierException.create(member);
|
||||
}
|
||||
var result = constructors.get(member);
|
||||
if (result == null) {
|
||||
throw UnknownIdentifierException.create(member);
|
||||
@ -308,6 +352,11 @@ public final class Type implements EnsoObject {
|
||||
return eigentype == this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a constructor in this type.
|
||||
*
|
||||
* @param constructor The constructor to register in this type.
|
||||
*/
|
||||
public void registerConstructor(AtomConstructor constructor) {
|
||||
constructors.put(constructor.getName(), constructor);
|
||||
gettersGenerated = false;
|
||||
|
@ -1,7 +1,9 @@
|
||||
package org.enso.interpreter.runtime.data.atom;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Idempotent;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
@ -134,40 +136,64 @@ public abstract class Atom implements EnsoObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a polyglot list of all the public members of Atom. A member is any method on the atom.
|
||||
* A member is public if it is not project-private.
|
||||
*/
|
||||
@ExportMessage
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
EnsoObject getMembers(boolean includeInternal) {
|
||||
Set<String> members =
|
||||
constructor.getDefinitionScope().getMethodNamesForType(constructor.getType());
|
||||
Set<String> allMembers = new HashSet<>();
|
||||
EnsoObject getMembers(boolean includeInternal) throws UnsupportedMessageException {
|
||||
Set<Function> members =
|
||||
constructor.getDefinitionScope().getMethodsForType(constructor.getType());
|
||||
Set<Function> allFuncMembers = new HashSet<>();
|
||||
if (members != null) {
|
||||
allMembers.addAll(members);
|
||||
allFuncMembers.addAll(members);
|
||||
}
|
||||
members =
|
||||
constructor.getType().getDefinitionScope().getMethodNamesForType(constructor.getType());
|
||||
members = constructor.getType().getDefinitionScope().getMethodsForType(constructor.getType());
|
||||
if (members != null) {
|
||||
allMembers.addAll(members);
|
||||
allFuncMembers.addAll(members);
|
||||
}
|
||||
String[] mems = allMembers.toArray(new String[0]);
|
||||
return ArrayLikeHelpers.wrapStrings(mems);
|
||||
String[] publicMembers =
|
||||
allFuncMembers.stream()
|
||||
.filter(method -> !method.getSchema().isProjectPrivate())
|
||||
.map(Function::getName)
|
||||
.distinct()
|
||||
.toArray(String[]::new);
|
||||
return ArrayLikeHelpers.wrapStrings(publicMembers);
|
||||
}
|
||||
|
||||
protected boolean isMethodProjectPrivate(Type type, String methodName) {
|
||||
Function method = constructor.getDefinitionScope().getMethodForType(type, methodName);
|
||||
if (method != null) {
|
||||
return method.getSchema().isProjectPrivate();
|
||||
}
|
||||
method = constructor.getType().getDefinitionScope().getMethodForType(type, methodName);
|
||||
return method != null && method.getSchema().isProjectPrivate();
|
||||
}
|
||||
|
||||
/** A member is invocable if it is a method on the Atom and it is public. */
|
||||
@ExportMessage
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
final boolean isMemberInvocable(String member) {
|
||||
Set<String> members =
|
||||
constructor.getDefinitionScope().getMethodNamesForType(constructor.getType());
|
||||
var type = constructor.getType();
|
||||
Set<String> members = constructor.getDefinitionScope().getMethodNamesForType(type);
|
||||
if (members != null && members.contains(member)) {
|
||||
return true;
|
||||
return !isMethodProjectPrivate(type, member);
|
||||
}
|
||||
members =
|
||||
constructor.getType().getDefinitionScope().getMethodNamesForType(constructor.getType());
|
||||
return members != null && members.contains(member);
|
||||
members = type.getDefinitionScope().getMethodNamesForType(type);
|
||||
if (members != null && members.contains(member)) {
|
||||
return !isMethodProjectPrivate(type, member);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** A member is readable if it is an atom constructor and if the atom constructor is public. */
|
||||
@ExportMessage
|
||||
@ExplodeLoop
|
||||
final boolean isMemberReadable(String member) {
|
||||
if (hasProjectPrivateConstructor()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < constructor.getArity(); i++) {
|
||||
if (member.equals(constructor.getFields()[i].getName())) {
|
||||
return true;
|
||||
@ -176,10 +202,22 @@ public abstract class Atom implements EnsoObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a field of the atom.
|
||||
*
|
||||
* @param member An identifier of a field.
|
||||
* @return Value of the member.
|
||||
* @throws UnknownIdentifierException If an unknown field is requested.
|
||||
* @throws UnsupportedMessageException If the atom constructor is project-private, and thus all
|
||||
* the fields are project-private.
|
||||
*/
|
||||
@ExportMessage
|
||||
@ExplodeLoop
|
||||
final Object readMember(String member, @CachedLibrary(limit = "3") StructsLibrary structs)
|
||||
throws UnknownIdentifierException {
|
||||
throws UnknownIdentifierException, UnsupportedMessageException {
|
||||
if (hasProjectPrivateConstructor()) {
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
for (int i = 0; i < constructor.getArity(); i++) {
|
||||
if (member.equals(constructor.getFields()[i].getName())) {
|
||||
return structs.getField(this, i);
|
||||
@ -188,6 +226,7 @@ public abstract class Atom implements EnsoObject {
|
||||
throw UnknownIdentifierException.create(member);
|
||||
}
|
||||
|
||||
/** Only public (non project-private) methods can be invoked. */
|
||||
@ExportMessage
|
||||
static class InvokeMember {
|
||||
|
||||
@ -196,7 +235,11 @@ public abstract class Atom implements EnsoObject {
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"receiver.getConstructor() == cachedConstructor", "member.equals(cachedMember)"},
|
||||
guards = {
|
||||
"receiver.getConstructor() == cachedConstructor",
|
||||
"member.equals(cachedMember)",
|
||||
"!isProjectPrivate(cachedConstructor, cachedMember)"
|
||||
},
|
||||
limit = "3")
|
||||
static Object doCached(
|
||||
Atom receiver,
|
||||
@ -210,6 +253,7 @@ public abstract class Atom implements EnsoObject {
|
||||
ArityException,
|
||||
UnsupportedTypeException,
|
||||
UnknownIdentifierException {
|
||||
assert !isProjectPrivate(cachedConstructor, cachedMember);
|
||||
Object[] args = new Object[arguments.length + 1];
|
||||
args[0] = receiver;
|
||||
System.arraycopy(arguments, 0, args, 1, arguments.length);
|
||||
@ -234,10 +278,24 @@ public abstract class Atom implements EnsoObject {
|
||||
ArityException,
|
||||
UnsupportedTypeException,
|
||||
UnknownIdentifierException {
|
||||
if (isProjectPrivate(receiver.getConstructor(), member)) {
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
UnresolvedSymbol symbol = buildSym(receiver.getConstructor(), member);
|
||||
return doCached(
|
||||
receiver, member, arguments, receiver.getConstructor(), member, symbol, symbols);
|
||||
}
|
||||
|
||||
@Idempotent
|
||||
@TruffleBoundary
|
||||
protected static boolean isProjectPrivate(AtomConstructor cons, String member) {
|
||||
Function method = cons.getDefinitionScope().getMethodForType(cons.getType(), member);
|
||||
if (method != null) {
|
||||
return method.getSchema().isProjectPrivate();
|
||||
}
|
||||
method = cons.getType().getDefinitionScope().getMethodForType(cons.getType(), member);
|
||||
return method != null && method.getSchema().isProjectPrivate();
|
||||
}
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
@ -305,4 +363,8 @@ public abstract class Atom implements EnsoObject {
|
||||
boolean hasMetaObject() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasProjectPrivateConstructor() {
|
||||
return constructor.getType().isProjectPrivate();
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public final class AtomConstructor implements EnsoObject {
|
||||
/**
|
||||
* Is the constructor initialized or not.
|
||||
*
|
||||
* @return {@code true} if {@link initializeFields} method has already been called
|
||||
* @return {@code true} if {@link #initializeFields} method has already been called
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return constructorFunction != null;
|
||||
@ -172,19 +172,25 @@ public final class AtomConstructor implements EnsoObject {
|
||||
MethodRootNode.buildConstructor(
|
||||
language, localScope, definitionScope, instantiateBlock, section, this);
|
||||
RootCallTarget callTarget = rootNode.getCallTarget();
|
||||
return new Function(callTarget, null, new FunctionSchema(annotations, args));
|
||||
var schemaBldr = FunctionSchema.newBuilder().annotations(annotations).argumentDefinitions(args);
|
||||
if (type.isProjectPrivate()) {
|
||||
schemaBldr.projectPrivate();
|
||||
}
|
||||
return new Function(callTarget, null, schemaBldr.build());
|
||||
}
|
||||
|
||||
private Function generateQualifiedAccessor(EnsoLanguage lang) {
|
||||
var node = new QualifiedAccessorNode(lang, this);
|
||||
var node = new QualifiedAccessorNode(lang, this, getDefinitionScope());
|
||||
var callTarget = node.getCallTarget();
|
||||
var function =
|
||||
new Function(
|
||||
callTarget,
|
||||
null,
|
||||
new FunctionSchema(
|
||||
var schemaBldr =
|
||||
FunctionSchema.newBuilder()
|
||||
.argumentDefinitions(
|
||||
new ArgumentDefinition(
|
||||
0, "self", null, null, ArgumentDefinition.ExecutionMode.EXECUTE)));
|
||||
0, "self", null, null, ArgumentDefinition.ExecutionMode.EXECUTE));
|
||||
if (type.isProjectPrivate()) {
|
||||
schemaBldr.projectPrivate();
|
||||
}
|
||||
var function = new Function(callTarget, null, schemaBldr.build());
|
||||
definitionScope.registerMethod(type.getEigentype(), this.name, function);
|
||||
return function;
|
||||
}
|
||||
@ -287,7 +293,7 @@ public final class AtomConstructor implements EnsoObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates field accessors for all fields in given constructors.
|
||||
* Creates field accessors for all fields in all constructors from the given type.
|
||||
*
|
||||
* @param language the language instance to create getters for
|
||||
* @param type type to create accessors for
|
||||
@ -319,7 +325,9 @@ public final class AtomConstructor implements EnsoObject {
|
||||
} else {
|
||||
var cons = constructors.toArray(AtomConstructor[]::new)[0];
|
||||
for (var field : cons.getFields()) {
|
||||
var node = new GetFieldNode(language, field.getPosition(), type, field.getName());
|
||||
var node =
|
||||
new GetFieldNode(
|
||||
language, field.getPosition(), type, field.getName(), cons.getDefinitionScope());
|
||||
roots.put(field.getName(), node);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
package org.enso.interpreter.runtime.data.atom;
|
||||
|
||||
import com.oracle.truffle.api.TruffleLanguage;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.nodes.RootNode;
|
||||
import org.enso.compiler.context.LocalScope;
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.interpreter.node.EnsoRootNode;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
|
||||
@NodeInfo(shortName = "get_field", description = "A base for auto-generated Atom getters.")
|
||||
final class GetFieldNode extends RootNode {
|
||||
final class GetFieldNode extends EnsoRootNode {
|
||||
private final int index;
|
||||
private final String name;
|
||||
private final Type type;
|
||||
@ -22,8 +23,8 @@ final class GetFieldNode extends RootNode {
|
||||
* @param language the current language instance.
|
||||
* @param index the index this node should use for field lookup.
|
||||
*/
|
||||
GetFieldNode(TruffleLanguage<?> language, int index, Type type, String name) {
|
||||
super(language);
|
||||
GetFieldNode(EnsoLanguage language, int index, Type type, String name, ModuleScope moduleScope) {
|
||||
super(language, LocalScope.root(), moduleScope, name, null);
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
@ -64,6 +65,6 @@ final class GetFieldNode extends RootNode {
|
||||
|
||||
@Override
|
||||
protected GetFieldNode cloneUninitialized() {
|
||||
return new GetFieldNode(getLanguage(EnsoLanguage.class), index, type, name);
|
||||
return new GetFieldNode(getLanguage(EnsoLanguage.class), index, type, name, getModuleScope());
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
package org.enso.interpreter.runtime.data.atom;
|
||||
|
||||
import com.oracle.truffle.api.TruffleLanguage;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.nodes.RootNode;
|
||||
import org.enso.compiler.context.LocalScope;
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.interpreter.node.EnsoRootNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
|
||||
@NodeInfo(
|
||||
shortName = "get_cons",
|
||||
description = "A base for auto-generated module-level atom constructor getters.")
|
||||
final class QualifiedAccessorNode extends RootNode {
|
||||
final class QualifiedAccessorNode extends EnsoRootNode {
|
||||
private final AtomConstructor atomConstructor;
|
||||
|
||||
/**
|
||||
@ -18,8 +20,14 @@ final class QualifiedAccessorNode extends RootNode {
|
||||
* @param language the current language instance.
|
||||
* @param atomConstructor the constructor to return.
|
||||
*/
|
||||
QualifiedAccessorNode(TruffleLanguage<?> language, AtomConstructor atomConstructor) {
|
||||
super(language);
|
||||
QualifiedAccessorNode(
|
||||
EnsoLanguage language, AtomConstructor atomConstructor, ModuleScope moduleScope) {
|
||||
super(
|
||||
language,
|
||||
LocalScope.root(),
|
||||
moduleScope,
|
||||
atomConstructor.getQualifiedName().toString(),
|
||||
null);
|
||||
this.atomConstructor = atomConstructor;
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ public final class ModuleScope implements EnsoObject {
|
||||
this.imports = new HashSet<>();
|
||||
this.exports = new HashSet<>();
|
||||
this.module = module;
|
||||
this.associatedType = Type.createSingleton(module.getName().item(), this, null, false);
|
||||
this.associatedType = Type.createSingleton(module.getName().item(), this, null, false, false);
|
||||
}
|
||||
|
||||
public ModuleScope(
|
||||
@ -103,7 +103,7 @@ public final class ModuleScope implements EnsoObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of methods defined in this module for a given constructor.
|
||||
* Returns a map of methods defined in this module for a given type.
|
||||
*
|
||||
* @param type the type for which method map is requested
|
||||
* @return a map containing all the defined methods by name
|
||||
@ -328,8 +328,8 @@ public final class ModuleScope implements EnsoObject {
|
||||
/**
|
||||
* Returns the names of methods for the given type.
|
||||
*
|
||||
* @param tpe the type in the scope
|
||||
* @return names of methods
|
||||
* @param tpe the type in the scope. If null, treated as {@code noType}.
|
||||
* @return names of methods or null
|
||||
*/
|
||||
public Set<String> getMethodNamesForType(Type tpe) {
|
||||
Type tpeKey = tpe == null ? noTypeKey : tpe;
|
||||
@ -337,6 +337,22 @@ public final class ModuleScope implements EnsoObject {
|
||||
return allTpeMethods == null ? null : allTpeMethods.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of all the functions for a type, or null.
|
||||
*
|
||||
* @param tpe the type in the scope. If null, treated as {@code noType}.
|
||||
* @return set of methods or null.
|
||||
*/
|
||||
public Set<Function> getMethodsForType(Type tpe) {
|
||||
Type tpeKey = tpe == null ? noTypeKey : tpe;
|
||||
var allTpeMethods = methods.get(tpeKey);
|
||||
if (allTpeMethods != null) {
|
||||
return allTpeMethods.values().stream().map(Supplier::get).collect(Collectors.toSet());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all methods of a type in the provided scope.
|
||||
*
|
||||
|
@ -42,6 +42,10 @@ public class ScalaConversions {
|
||||
return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail);
|
||||
}
|
||||
|
||||
public static <T> scala.collection.immutable.Seq<T> seq(List<T> list) {
|
||||
return CollectionConverters.asScala(list).toSeq();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Scala set from the provided elements.
|
||||
*
|
||||
|
@ -521,13 +521,14 @@ class IrToTruffle(
|
||||
builtinRootNode
|
||||
.setModuleName(moduleScope.getModule.getName)
|
||||
builtinRootNode.setTypeName(cons.getQualifiedName)
|
||||
val funcSchema = FunctionSchema
|
||||
.newBuilder()
|
||||
.argumentDefinitions(bodyBuilder.args(): _*)
|
||||
.build()
|
||||
new RuntimeFunction(
|
||||
m.getFunction.getCallTarget,
|
||||
null,
|
||||
new FunctionSchema(
|
||||
new Array[RuntimeAnnotation](0),
|
||||
bodyBuilder.args(): _*
|
||||
)
|
||||
funcSchema
|
||||
)
|
||||
} else {
|
||||
m.getFunction
|
||||
@ -632,12 +633,17 @@ class IrToTruffle(
|
||||
}
|
||||
}
|
||||
|
||||
val funcSchema = FunctionSchema
|
||||
.newBuilder()
|
||||
.annotations(annotations: _*)
|
||||
.argumentDefinitions(arguments: _*)
|
||||
.build()
|
||||
Right(
|
||||
Some(
|
||||
new RuntimeFunction(
|
||||
callTarget,
|
||||
null,
|
||||
new FunctionSchema(annotations.toArray, arguments: _*)
|
||||
funcSchema
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -716,10 +722,14 @@ class IrToTruffle(
|
||||
)
|
||||
val callTarget = rootNode.getCallTarget
|
||||
val arguments = bodyBuilder.args()
|
||||
val funcSchema = FunctionSchema
|
||||
.newBuilder()
|
||||
.argumentDefinitions(arguments: _*)
|
||||
.build()
|
||||
new RuntimeFunction(
|
||||
callTarget,
|
||||
null,
|
||||
new FunctionSchema(arguments: _*)
|
||||
funcSchema
|
||||
)
|
||||
case _ =>
|
||||
throw new CompilerError(
|
||||
@ -904,10 +914,9 @@ class IrToTruffle(
|
||||
constructor.getAccessorFunction()
|
||||
|
||||
def mkTypeGetter(tp: Type): RuntimeFunction = {
|
||||
new RuntimeFunction(
|
||||
new ConstantNode(language, tp).getCallTarget,
|
||||
null,
|
||||
new FunctionSchema(
|
||||
val funcSchema = FunctionSchema
|
||||
.newBuilder()
|
||||
.argumentDefinitions(
|
||||
new ArgumentDefinition(
|
||||
0,
|
||||
ConstantsNames.SELF_ARGUMENT,
|
||||
@ -916,6 +925,11 @@ class IrToTruffle(
|
||||
ArgumentDefinition.ExecutionMode.EXECUTE
|
||||
)
|
||||
)
|
||||
.build()
|
||||
new RuntimeFunction(
|
||||
new ConstantNode(language, tp).getCallTarget,
|
||||
null,
|
||||
funcSchema
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -48,15 +48,36 @@ class RuntimeStubsGenerator(builtins: Builtins) {
|
||||
scope.registerType(builtinType.getType)
|
||||
builtinType.getType.setShadowDefinitions(scope, true)
|
||||
} else {
|
||||
val isTypeProjectPrivate =
|
||||
tp.members.nonEmpty && tp.members.forall(_.isProjectPrivate)
|
||||
val createdType =
|
||||
if (tp.members.nonEmpty || tp.builtinType) {
|
||||
Type.create(tp.name, scope, builtins.any(), builtins.any(), false)
|
||||
Type.create(
|
||||
tp.name,
|
||||
scope,
|
||||
builtins.any(),
|
||||
builtins.any(),
|
||||
false,
|
||||
isTypeProjectPrivate
|
||||
)
|
||||
} else {
|
||||
Type.createSingleton(tp.name, scope, builtins.any(), false)
|
||||
Type.createSingleton(
|
||||
tp.name,
|
||||
scope,
|
||||
builtins.any(),
|
||||
false,
|
||||
isTypeProjectPrivate
|
||||
)
|
||||
}
|
||||
val rtp = scope.registerType(createdType)
|
||||
tp.members.foreach { cons =>
|
||||
val constructor = new AtomConstructor(cons.name, scope, rtp)
|
||||
val constructor =
|
||||
new AtomConstructor(
|
||||
cons.name,
|
||||
scope,
|
||||
rtp,
|
||||
false
|
||||
)
|
||||
rtp.registerConstructor(constructor)
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +256,66 @@ fn type_constructors() {
|
||||
test(&code.join("\n"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_constructor_private() {
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"type Foo",
|
||||
" private Bar"
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let expected = block![
|
||||
(TypeDef type Foo #()
|
||||
#((Private (ConstructorDefinition Bar #() #()))))];
|
||||
test(&code.join("\n"), expected);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"type Foo",
|
||||
" private Bar",
|
||||
" Foo"
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let expected = block![
|
||||
(TypeDef type Foo #()
|
||||
#((Private (ConstructorDefinition Bar #() #()))
|
||||
(ConstructorDefinition Foo #() #()))
|
||||
)
|
||||
];
|
||||
test(&code.join("\n"), expected);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"type Geo",
|
||||
" private Circle",
|
||||
" radius",
|
||||
" x",
|
||||
" Rectangle width height",
|
||||
" Point",
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let expected = block![
|
||||
(TypeDef type Geo #()
|
||||
#((Private(ConstructorDefinition
|
||||
Circle #() #(((() (Ident radius) () ())) ((() (Ident x) () ())))))
|
||||
(ConstructorDefinition
|
||||
Rectangle #((() (Ident width) () ()) (() (Ident height) () ())) #())
|
||||
(ConstructorDefinition Point #() #())))];
|
||||
test(&code.join("\n"), expected);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"type My_Type",
|
||||
" private Value a b c"
|
||||
];
|
||||
let expected = block![
|
||||
(TypeDef type My_Type #()
|
||||
#((Private (ConstructorDefinition Value #((() (Ident a) () ()) (() (Ident b) () ()) (() (Ident c) () ())) #())))
|
||||
)
|
||||
];
|
||||
test(&code.join("\n"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_methods() {
|
||||
let code = ["type Geo", " number =", " x", " area self = x + x"];
|
||||
@ -1309,10 +1369,43 @@ fn pattern_match_suspended_default_arguments() {
|
||||
}
|
||||
|
||||
// === Private (project-private) keyword ===
|
||||
// So far, private keyword is only allowed to either have empty body, or to be followed by
|
||||
// a constructor definition.
|
||||
// Nothing else can be private yet.
|
||||
#[test]
|
||||
fn private_keyword() {
|
||||
test("private", block![(Private())]);
|
||||
test("private func", block![(Private (Ident func))]);
|
||||
expect_invalid_node("private func");
|
||||
|
||||
expect_invalid_node("private ConstructorOutsideType");
|
||||
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"type My_Type",
|
||||
" private method self = self.x"
|
||||
];
|
||||
expect_invalid_node(&code.join("\n"));
|
||||
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"type My_Type",
|
||||
" private"
|
||||
];
|
||||
expect_invalid_node(&code.join("\n"));
|
||||
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"pub_method x y = x + y",
|
||||
"private priv_method x y = x + y"
|
||||
];
|
||||
expect_invalid_node(&code.join("\n"));
|
||||
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"private type My_Type",
|
||||
" Ctor"
|
||||
];
|
||||
expect_invalid_node(&code.join("\n"));
|
||||
}
|
||||
|
||||
|
||||
|
@ -337,6 +337,35 @@ fn type_def_body<'s>(
|
||||
|
||||
fn to_body_statement(mut line_expression: syntax::Tree<'_>) -> syntax::Tree<'_> {
|
||||
use syntax::tree::*;
|
||||
|
||||
// Unwrap `Private` tree from any `Invalid` added in expression context; it will be revalidated
|
||||
// in the new context.
|
||||
if let Tree {
|
||||
variant:
|
||||
box Variant::Invalid(Invalid {
|
||||
ast: mut inner @ Tree { variant: box Variant::Private(_), .. },
|
||||
..
|
||||
}),
|
||||
span,
|
||||
} = line_expression
|
||||
{
|
||||
inner.span = span;
|
||||
return to_body_statement(inner);
|
||||
}
|
||||
// Recurse into body of `Private` keyword; validate usage of the keyword in type-body context.
|
||||
if let Tree { variant: box Variant::Private(ref mut private), .. } = &mut line_expression {
|
||||
let body_statement = private.body.take().map(to_body_statement);
|
||||
let error = match body_statement.as_ref().map(|tree| &*tree.variant) {
|
||||
Some(Variant::ConstructorDefinition(_)) => None,
|
||||
None => Some("Expected declaration after `private` keyword in type definition."),
|
||||
_ => Some("The `private` keyword inside a type definition may only be applied to a constructor definition."),
|
||||
};
|
||||
private.body = body_statement;
|
||||
return match error {
|
||||
Some(error) => line_expression.with_error(error),
|
||||
None => line_expression,
|
||||
};
|
||||
}
|
||||
if let Tree { variant: box Variant::Documented(Documented { expression, .. }), .. } =
|
||||
&mut line_expression
|
||||
{
|
||||
@ -715,14 +744,27 @@ fn freeze<'s>() -> Definition<'s> {
|
||||
crate::macro_definition! {("FREEZE", everything()) capture_expressions}
|
||||
}
|
||||
|
||||
/// private can be either specified as the very first statement in the module, marking the
|
||||
/// whole module as private. Or it can be prepended to some definitions. For example it can
|
||||
/// be prepended to atom constructor definition.
|
||||
fn private_keyword<'s>(
|
||||
segments: NonEmptyVec<MatchedSegment<'s>>,
|
||||
precedence: &mut operator::Precedence<'s>,
|
||||
) -> syntax::Tree<'s> {
|
||||
let segment = segments.pop().0;
|
||||
let keyword = into_private(segment.header);
|
||||
let body = precedence.resolve(segment.result.tokens());
|
||||
syntax::Tree::private(keyword, body)
|
||||
let body_opt = precedence.resolve(segment.result.tokens());
|
||||
match body_opt {
|
||||
Some(body) => {
|
||||
syntax::Tree::private(keyword, Some(body))
|
||||
.with_error("The 'private' keyword cannot be applied to any expression outside of a type definition")
|
||||
}
|
||||
None => {
|
||||
// Just a private keyword without a body. This is valid as the first statement in the
|
||||
// module, to declare the module as private.
|
||||
syntax::Tree::private(keyword, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro body builder that just parses the tokens of each segment as expressions, and places them
|
||||
|
@ -42,6 +42,15 @@ pub struct Location {
|
||||
pub col16: u32,
|
||||
}
|
||||
|
||||
impl Display for Location {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Location{{")?;
|
||||
write!(f, "utf8 = {utf8}, utf16 = {utf16}, ", utf8 = self.utf8, utf16 = self.utf16)?;
|
||||
write!(f, "line = {line}, col16 = {col16}", line = self.line, col16 = self.col16)?;
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Length> for Location {
|
||||
type Output = Self;
|
||||
|
||||
@ -386,4 +395,14 @@ pub mod debug {
|
||||
assert_eq!(&non_char_boundary_locations, &[]);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LocationCheck {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "LocationCheck{{")?;
|
||||
for (k, v) in &self.locations {
|
||||
write!(f, "{k}: {v}")?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,3 +5,5 @@ version: 0.0.1
|
||||
license: MIT
|
||||
author: enso-dev@enso.org
|
||||
maintainer: enso-dev@enso.org
|
||||
# Base_Tests import stuff from Helpers sibling project. So we need this property here.
|
||||
prefer-local-libraries: true
|
||||
|
@ -16,6 +16,7 @@ import project.Semantic.Equals_Spec
|
||||
import project.Semantic.Runtime_Spec
|
||||
import project.Semantic.Self_Type_Spec
|
||||
import project.Semantic.Warnings_Spec
|
||||
import project.Semantic.Private_Spec
|
||||
|
||||
import project.Semantic.Java_Interop_Spec
|
||||
import project.Semantic.Js_Interop_Spec
|
||||
@ -168,6 +169,7 @@ main filter=Nothing =
|
||||
Statistics_Spec.add_specs suite_builder
|
||||
Regression_Spec.add_specs suite_builder
|
||||
Warnings_Spec.add_specs suite_builder
|
||||
Private_Spec.add_specs suite_builder
|
||||
System_Spec.add_specs suite_builder
|
||||
Random_Spec.add_specs suite_builder
|
||||
XML_Spec.add_specs suite_builder
|
||||
|
87
test/Base_Tests/src/Semantic/Private_Spec.enso
Normal file
87
test/Base_Tests/src/Semantic/Private_Spec.enso
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Errors.Common.Private_Access
|
||||
from Standard.Test import all
|
||||
from enso_dev.Helpers import Priv_Type
|
||||
|
||||
polyglot java import org.enso.base_test_helpers.CallbackHelper
|
||||
|
||||
type My_Priv_Type
|
||||
private Cons data
|
||||
|
||||
|
||||
add_specs spec_builder =
|
||||
spec_builder.group "Private constructors" group_builder->
|
||||
group_builder.specify "cannot directly call private constructor" <|
|
||||
Test.expect_panic Private_Access <| Priv_Type.Cons 42
|
||||
|
||||
# Optimally, we want `Priv_Type.Cons` expression to result in a compilation error.
|
||||
# All the references to private methods/constructors are theoretically resolvable
|
||||
# at compilation time. However, the current state of the static compiler does not
|
||||
# allow us to do that easily.
|
||||
# For more info, see https://github.com/enso-org/enso/issues/6729
|
||||
group_builder.specify "can get reference to private constructor, but cannot call it" <|
|
||||
cons_fn data = Priv_Type.Cons data
|
||||
Test.expect_panic Private_Access <| cons_fn 42
|
||||
|
||||
group_builder.specify "can call private constructor via public factory method" <|
|
||||
obj = Priv_Type.create 42
|
||||
obj.is_nothing . should_be_false
|
||||
|
||||
group_builder.specify "cannot get private field" <|
|
||||
obj = Priv_Type.create 42
|
||||
Test.expect_panic Private_Access <| obj.data
|
||||
|
||||
group_builder.specify "can get private field via public accessor" <|
|
||||
obj = Priv_Type.create 42
|
||||
obj.get_data . should_equal 42
|
||||
|
||||
group_builder.specify "cannot get private field from JS" <|
|
||||
obj = Priv_Type.create 42
|
||||
# When JS tries to access a private field, it does not throw a panic, as it does not
|
||||
# see the field. It returns undefined.
|
||||
ret = js_access_field obj
|
||||
ret.is_nothing . should_be_true
|
||||
|
||||
group_builder.specify "can call private constructor via public factory method from JS" <|
|
||||
create_fn x = Priv_Type.create x
|
||||
obj = js_create_obj create_fn
|
||||
obj.is_nothing . should_be_false
|
||||
|
||||
group_builder.specify "can get private field via public accessor from JS" <|
|
||||
obj = Priv_Type.create 42
|
||||
js_access_field_via_getter obj . should_equal 42
|
||||
|
||||
group_builder.specify "cannot call private constructor from Java" <|
|
||||
cons_fn data = Priv_Type.Cons data
|
||||
Test.expect_panic Private_Access <| CallbackHelper.runCallbackInt cons_fn 42
|
||||
|
||||
group_builder.specify "cannot call private constructor from a lambda method" <|
|
||||
Test.expect_panic Private_Access <|
|
||||
Priv_Type.in_ctx <|
|
||||
Priv_Type.Cons 42
|
||||
|
||||
# Note that pattern matching on project-private constructor from a different project
|
||||
# is a compilation error. So we can only test it for the same project.
|
||||
group_builder.specify "can pattern match on private constructor from the same project" <|
|
||||
obj = My_Priv_Type.Cons 42
|
||||
res = case obj of
|
||||
My_Priv_Type.Cons x -> x
|
||||
_ -> 0
|
||||
res . should_equal 42
|
||||
|
||||
|
||||
foreign js js_access_field obj = """
|
||||
return obj.data;
|
||||
|
||||
foreign js js_access_field_via_getter obj = """
|
||||
return obj.get_data();
|
||||
|
||||
foreign js js_create_obj cons_fn = """
|
||||
return cons_fn(42);
|
||||
|
||||
|
||||
main filter=Nothing =
|
||||
suite = Test.build suite_builder->
|
||||
add_specs suite_builder
|
||||
suite.run_with_filter filter
|
4
test/Helpers/README.md
Normal file
4
test/Helpers/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
Just a simple test project that contains some private-project entities that can
|
||||
be called from different test projects.
|
||||
|
||||
This project is not meant to be run directly.
|
7
test/Helpers/package.yaml
Normal file
7
test/Helpers/package.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
name: Helpers
|
||||
namespace: enso_dev
|
||||
enso-version: default
|
||||
version: 0.0.1
|
||||
license: MIT
|
||||
author: enso-dev@enso.org
|
||||
maintainer: enso-dev@enso.org
|
15
test/Helpers/src/Main.enso
Normal file
15
test/Helpers/src/Main.enso
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
type Priv_Type
|
||||
## Both constructor `Cons` and field `data` are private
|
||||
private Cons data
|
||||
|
||||
## Public factory method
|
||||
create data = Priv_Type.Cons data
|
||||
|
||||
## Public accessor for the private `data` field
|
||||
get_data self = self.data
|
||||
|
||||
in_ctx ~action =
|
||||
result = action
|
||||
result
|
||||
|
@ -25,7 +25,7 @@ add_specs suite_builder =
|
||||
enter d1844837-30e7-46b7-bde3-72f7afec52cf
|
||||
result d1844837-30e7-46b7-bde3-72f7afec52cf result: UnresolvedSymbol<Count> type: Function
|
||||
enter 00e33517-6d19-4f5a-84c5-126f59a93b67
|
||||
callfn 00e33517-6d19-4f5a-84c5-126f59a93b67 fn: null self=_ args: [Aggregate_Column]
|
||||
callfn 00e33517-6d19-4f5a-84c5-126f59a93b67 fn: Standard.Table.Aggregate_Column.Aggregate_Column.Count self=_ args: [Aggregate_Column]
|
||||
result 00e33517-6d19-4f5a-84c5-126f59a93b67 result: (Count '') type: Aggregate_Column
|
||||
|
||||
pair.first.column_names . should_equal pair.second.column_names
|
||||
|
Loading…
Reference in New Issue
Block a user