Skip to content

Commit

Permalink
Add extension methods restrictions to exports
Browse files Browse the repository at this point in the history
Similarly to imports, we needed to extend the logic of exports to take
into account qualified exports.

Added a bunch of multi-level tests, fixed an existing one that shouldn't
be working.
  • Loading branch information
hubertp committed Nov 29, 2022
1 parent 05c158e commit 9b1df42
Show file tree
Hide file tree
Showing 26 changed files with 168 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.oracle.truffle.api.source.SourceSection;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -436,17 +437,12 @@ public ModuleScope getScope() {
}

/**
* Returns the runtime scope of this module that filters out only the requested types. If the list
* of requested types is empty, returns the unchanged runtime scope.
* Returns the runtime scope of this module that filters out only the requested types.
*
* @param types a list of types to include in the scope
*/
public ModuleScope getScope(List<String> types) {
if (types.isEmpty()) {
return scope;
} else {
return scope.withTypes(types);
}
public ModuleScope getScope(Collection<String> types) {
return scope.withTypes(types);
}

/** @return the qualified name of this module. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public void reset() {
* @param typeNames list of types to copy to the new scope
* @return a copy of this scope modulo the requested types
*/
public ModuleScope withTypes(List<String> typeNames) {
public ModuleScope withTypes(Collection<String> typeNames) {
Map<String, Object> polyglotSymbols = new HashMap<>(this.polyglotSymbols);
Map<String, Type> types = new HashMap<>(this.types);
Map<Type, Map<String, Function>> methods = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,18 @@ class IrToTruffle(
)

bindingsMap.resolvedExports
.collect { case ExportedModule(ResolvedModule(module), _, _) =>
module
.collect {
case ExportedModule(ResolvedModule(module), _, symbolRestrictions) =>
(module, symbolRestrictions)
}
.foreach { exp =>
moduleScope.addExport(exp.unsafeAsModule().getScope)
.foreach { case (exp, restrictions) =>
val mod = exp.unsafeAsModule()
val scope = if (restrictions.canAccessExtensionMethods()) {
mod.getScope()
} else {
mod.getScope(mod.getScope().getTypes.keySet())
}
moduleScope.addExport(scope)
}
val imports = module.imports
val methodDefs = module.bindings.collect {
Expand All @@ -180,9 +187,13 @@ class IrToTruffle(
case BindingsMap.ResolvedType(_, _) =>
case ResolvedModule(module) =>
val mod = module.unsafeAsModule()
val scope: ModuleScope = imp.importDef.onlyNames
.map(only => mod.getScope(only.map(_.name).asJava))
.getOrElse(mod.getScope())
val scope: ModuleScope =
imp.importDef.onlyNames match {
case None =>
mod.getScope()
case Some(only) =>
mod.getScope(only.map(_.name).asJava)
}
moduleScope.addImport(scope)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,13 @@ object BindingsMap {
*/
def canAccess(symbol: String, resolution: ResolvedName): Boolean

/** Whether the export statement allows accessing extension methods
* that may be defined in the underlying module.
*
* @return whether extension methods are exported and therefore accessible
*/
def canAccessExtensionMethods(): Boolean

/** Performs static optimizations on the restriction, simplifying
* common patterns.
*
Expand Down Expand Up @@ -380,6 +387,17 @@ object BindingsMap {
symbolMatch && resolutionMatch
}

/** Checks if the extension methods can be exported under this restriction.
*
* @return `true` if the extension method can be exported, `false` otherwise.
*/
def allowsExtensionMethods(): Boolean = {
resolution.map {
case _: ResolvedModule => true
case _ => false
} getOrElse false
}

/** Convert the internal resolution to abstract form.
*
* @return `this` with its resolution converted to abstract form
Expand Down Expand Up @@ -414,6 +432,10 @@ object BindingsMap {
resolution: ResolvedName
): Boolean = symbols.exists(_.allows(symbol, resolution))

/** @inheritdoc */
override def canAccessExtensionMethods(): Boolean =
symbols.size == 1 && symbols.head.allowsExtensionMethods()

/** @inheritdoc */
override def optimize: SymbolRestriction = this

Expand Down Expand Up @@ -444,6 +466,10 @@ object BindingsMap {
resolution: ResolvedName
): Boolean = !symbols.contains(symbol.toLowerCase)

/** @inheritdoc */
override def canAccessExtensionMethods(): Boolean =
true

/** @inheritdoc */
override def optimize: Hiding = this

Expand All @@ -452,6 +478,7 @@ object BindingsMap {

/** @inheritdoc */
override def toConcrete(moduleMap: ModuleMap): Option[Hiding] = Some(this)

}

/** A restriction meaning there's no restriction at all.
Expand All @@ -464,6 +491,10 @@ object BindingsMap {
resolution: ResolvedName
): Boolean = true

/** @inheritdoc */
override def canAccessExtensionMethods(): Boolean =
true

/** @inheritdoc */
override def optimize: SymbolRestriction = this

Expand All @@ -486,6 +517,10 @@ object BindingsMap {
resolution: ResolvedName
): Boolean = false

/** @inheritdoc */
override def canAccessExtensionMethods(): Boolean =
false

/** @inheritdoc */
override def optimize: SymbolRestriction = this

Expand All @@ -512,6 +547,10 @@ object BindingsMap {
resolution: ResolvedName
): Boolean = restrictions.forall(_.canAccess(symbol, resolution))

/** @inheritdoc */
override def canAccessExtensionMethods(): Boolean =
restrictions.forall(_.canAccessExtensionMethods())

/** @inheritdoc */
//noinspection DuplicatedCode
override def optimize: SymbolRestriction = {
Expand Down Expand Up @@ -574,6 +613,10 @@ object BindingsMap {
resolution: ResolvedName
): Boolean = restrictions.exists(_.canAccess(symbol, resolution))

/** @inheritdoc */
override def canAccessExtensionMethods(): Boolean =
restrictions.exists(_.canAccessExtensionMethods())

/** @inheritdoc */
//noinspection DuplicatedCode
override def optimize: SymbolRestriction = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Test_Extension_Methods_Failure_2
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <[email protected]>"
maintainer: "Enso Team <[email protected]>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from Standard.Base.Data.Numbers import Integer

type Foo
A
B

Integer.foo self a = 1 + a
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from project.F1 import all
from project.F1 export Foo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from project.F2 import all

main =
1.foo 41
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Test_Extension_Methods_Success
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <[email protected]>"
maintainer: "Enso Team <[email protected]>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from Standard.Base.Data.Numbers import Integer

type Foo
A
B

Integer.foo self a = 1 + a
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import project.F1
export project.F1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from project.F2 import all

main =
1.foo 41
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Test_Extension_Methods_Success_4
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <[email protected]>"
maintainer: "Enso Team <[email protected]>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from Standard.Base.Data.Numbers import Integer

type Foo
A
B

Integer.foo self a = 1 + a
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from project.F1 import all
from project.F1 export all
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from project.F2 import all

main =
1.foo 41
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Test_Extension_Methods_Success_4
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <[email protected]>"
maintainer: "Enso Team <[email protected]>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from Standard.Base.Data.Numbers import Integer

type Foo
A
B

bar a = 1 + a
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from project.F1 import bar
from project.F1 export bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from project.F2 import all

main =
bar 41
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,38 @@ class ImportsTest extends PackageTest {
outLines(3) shouldEqual "(Mk_C 10)"
}

"Importing module" should "bring extension methods into the scope " in {
"Importing module" should "bring extension methods into the scope" in {
evalTestProject("Test_Extension_Methods_Success_1") shouldEqual 42
}

"The unqualified import of a module" should "bring extension methods into the scope " in {
"The unqualified import of a module" should "bring extension methods into the scope" in {
evalTestProject("Test_Extension_Methods_Success_2") shouldEqual 42
}

"Importing module's types" should "not bring extension methods into the scope " in {
"The unqualified import of a module" should "bring module exported extension methods into the scope" in {
evalTestProject("Test_Extension_Methods_Success_3") shouldEqual 42
}

"The unqualified import of a module" should "bring exported extension methods into the scope" in {
evalTestProject("Test_Extension_Methods_Success_4") shouldEqual 42
}

"Importing module's types" should "not bring extension methods into the scope" in {
the[InterpreterException] thrownBy evalTestProject(
"Test_Extension_Methods_Failure_1"
) should have message "Method `foo` of 1 (Integer) could not be found."
}

"Importing module" should "respect exports when bringing extension methods into the scope" in {
the[InterpreterException] thrownBy evalTestProject(
"Test_Extension_Methods_Failure"
"Test_Extension_Methods_Failure_2"
) should have message "Method `foo` of 1 (Integer) could not be found."
}

"Importing individual methods" should "bringing them into the scope" in {
evalTestProject("Test_Import_Methods_Success") shouldEqual 42
}

"Compiler" should "detect name conflicts preventing users from importing submodules" in {
the[InterpreterException] thrownBy evalTestProject(
"TestSubmodulesNameConflict"
Expand Down
1 change: 1 addition & 0 deletions test/Tests/src/Semantic/Deep_Export/Internal_4.enso
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import project.Semantic.Deep_Export.Internal_5

from project.Semantic.Deep_Export.Internal_5 export const
export project.Semantic.Deep_Export.Internal_5
4 changes: 3 additions & 1 deletion test/Tests/src/Semantic/Deep_Export/Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ from Standard.Base import all

import project.Semantic.Deep_Export.Internal

from Standard.Test import Test
from Standard.Test import Test, Test_Suite
import Standard.Test.Extensions

spec =
Test.group "Deep Exports" <|
Test.specify "should allow to re-export a symbol through a module hierarchy" <|
Internal.my_fun.should_equal 478

main = Test_Suite.run_main spec

0 comments on commit 9b1df42

Please sign in to comment.