Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
- [Require opt-in to prefer local libraries][14885]
- [One log file per execution][14893]
- [Opt-in to get more detailed logs][14895]
- [`from Xyz import to_text` can import extension methods][14949]

[14480]: https://github.com/enso-org/enso/pull/14480
[14490]: https://github.com/enso-org/enso/pull/14490
Expand All @@ -96,6 +97,7 @@
[14885]: https://github.com/enso-org/enso/pull/14885
[14893]: https://github.com/enso-org/enso/pull/14893
[14895]: https://github.com/enso-org/enso/pull/14895
[14949]: https://github.com/enso-org/enso/pull/14949

# Enso 2025.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -20,6 +22,7 @@
import org.enso.test.utils.ContextUtils;
import org.enso.test.utils.ProjectUtils;
import org.enso.test.utils.SourceModule;
import org.graalvm.polyglot.PolyglotException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -236,6 +239,218 @@ public void exportSyntheticModule() throws IOException {
}
}

@Test
public void exportExtensionMethodViaAll() throws IOException {
var rawMod =
new SourceModule(
QualifiedName.fromString("Raw_Module"),
"""
type Raw_Type
""");
var extMod =
new SourceModule(
QualifiedName.fromString("Ext_Module"),
"""
import project.Raw_Module.Raw_Type

Raw_Type.enhanced_method = 42
""");
ProjectUtils.createProject("Enhancing", Set.of(rawMod, extMod), projDir);
try (var ctx = createCtx(projDir)) {
compile(ctx);

var mainValue =
ctx.evalModule(
"""
import local.Enhancing.Raw_Module.Raw_Type
from local.Enhancing.Ext_Module import all

main = Raw_Type.enhanced_method
""");
assertEquals(42, mainValue.asInt());
}
}

@Test
public void exportExtensionMethodByName() throws IOException {
var rawMod =
new SourceModule(
QualifiedName.fromString("Raw_Module"),
"""
type Raw_Type
""");
var extMod =
new SourceModule(
QualifiedName.fromString("Ext_Module"),
"""
import project.Raw_Module.Raw_Type

Raw_Type.enhanced_method = 42
""");
ProjectUtils.createProject("Enhancing", Set.of(rawMod, extMod), projDir);
try (var ctx = createCtx(projDir)) {
compile(ctx);

var mainValue =
ctx.evalModule(
"""
import local.Enhancing.Raw_Module.Raw_Type
from local.Enhancing.Ext_Module import enhanced_method

main = Raw_Type.enhanced_method
""");
assertEquals(42, mainValue.asInt());
}
}

@Test
public void exportExtensionMethodByAllWithHiding() throws IOException {
var rawMod =
new SourceModule(
QualifiedName.fromString("Raw_Module"),
"""
type Raw_Type
""");
var extMod =
new SourceModule(
QualifiedName.fromString("Ext_Module"),
"""
import project.Raw_Module.Raw_Type

Raw_Type.enhanced_method = 42
Raw_Type.wrong_method = 33
""");
ProjectUtils.createProject("Enhancing", Set.of(rawMod, extMod), projDir);
try (var ctx = createCtx(projDir)) {
compile(ctx);

var mainValue =
ctx.evalModule(
"""
import local.Enhancing.Raw_Module.Raw_Type
from local.Enhancing.Ext_Module import all hiding wrong_method

main = Raw_Type.enhanced_method
""");
assertEquals(42, mainValue.asInt());
}
}

@Test
public void exportExtensionWrongMethod() throws IOException {
var rawMod =
new SourceModule(
QualifiedName.fromString("Raw_Module"),
"""
type Raw_Type
""");
var extMod =
new SourceModule(
QualifiedName.fromString("Ext_Module"),
"""
import project.Raw_Module.Raw_Type

Raw_Type.enhanced_method = 42
Raw_Type.wrong_method = 33
""");
ProjectUtils.createProject("Enhancing", Set.of(rawMod, extMod), projDir);
try (var ctx = createCtx(projDir)) {
compile(ctx);

try {

var mainValue =
ctx.evalModule(
"""
import local.Enhancing.Raw_Module.Raw_Type
from local.Enhancing.Ext_Module import wrong_method

main = Raw_Type.enhanced_method
""");
fail("Expecting exception, not a value: " + mainValue);
} catch (PolyglotException ex) {
assertEquals(
"Method `enhanced_method` of type Raw_Type could not be found.", ex.getMessage());
}
}
}

@Test
public void exportExtensionHidingTheRightMethod() throws IOException {
var rawMod =
new SourceModule(
QualifiedName.fromString("Raw_Module"),
"""
type Raw_Type
""");
var extMod =
new SourceModule(
QualifiedName.fromString("Ext_Module"),
"""
import project.Raw_Module.Raw_Type

Raw_Type.enhanced_method = 42
Raw_Type.wrong_method = 33
""");
ProjectUtils.createProject("Enhancing", Set.of(rawMod, extMod), projDir);
try (var ctx = createCtx(projDir)) {
compile(ctx);

try {

var mainValue =
ctx.evalModule(
"""
import local.Enhancing.Raw_Module.Raw_Type
from local.Enhancing.Ext_Module import all hiding enhanced_method

main = Raw_Type.enhanced_method
""");
fail("Expecting exception, not a value: " + mainValue);
} catch (PolyglotException ex) {
assertEquals(
"Method `enhanced_method` of type Raw_Type could not be found.", ex.getMessage());
}
}
}

@Test
public void exportConversionWhileHidingSomething() throws IOException {
var rawMod =
new SourceModule(
QualifiedName.fromString("Raw_Module"),
"""
type Raw_Type
""");
var extMod =
new SourceModule(
QualifiedName.fromString("Ext_Module"),
"""
from Standard.Base import Integer
import project.Raw_Module.Raw_Type

something = 33
Integer.from (_:Raw_Type) = 42
""");
ProjectUtils.createProject("Enhancing", Set.of(rawMod, extMod), projDir);
try (var ctx = createCtx(projDir)) {
compile(ctx);

var mainValue =
ctx.evalModule(
"""
from Standard.Base import Integer
import local.Enhancing.Raw_Module.Raw_Type
from local.Enhancing.Ext_Module import all hiding something

main =
fourtyTwo = Raw_Type:Integer
fourtyTwo
""");
assertEquals("Conversion from Raw_Type to Integer found", 42, mainValue.asInt());
}
}

private static ContextUtils createCtx(Path projDir) {
return ContextUtils.newBuilder().withProjectRoot(projDir).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,44 @@
* A proxy scope delegating to the underlying module's scope. Additionally, `ImportExportScope` may
* limit the number of types that are imported/exported.
*/
public class ImportExportScope extends EnsoObject {
public final class ImportExportScope extends EnsoObject {

private final Module module;
private final List<String> typesOnlyNames;
private final List<String> onlyNames;
private final List<String> hiddenNames;

public ImportExportScope(CompilerContext.Module module, List<String> typesOnlyNames) {
public ImportExportScope(
CompilerContext.Module module, List<String> onlyNames, List<String> hiddenNames) {
this.module = org.enso.interpreter.runtime.Module.fromCompilerModule(module);
this.typesOnlyNames =
typesOnlyNames != null && !typesOnlyNames.isEmpty() ? typesOnlyNames : null;
this.onlyNames = onlyNames != null && !onlyNames.isEmpty() ? onlyNames : null;
this.hiddenNames = hiddenNames != null && !hiddenNames.isEmpty() ? hiddenNames : null;
}

public ImportExportScope(CompilerContext.Module module) {
this.module = org.enso.interpreter.runtime.Module.fromCompilerModule(module);
this.typesOnlyNames = null;
private boolean isValidTypeOrSymbol(Type type, String symbol) {
if (onlyNames == null) {
if (hiddenNames != null) {
if (symbol != null && hiddenNames.contains(symbol)) {
return false;
}
if (hiddenNames.contains(type.getName())) {
return false;
}
}
return true;
} else {
if (onlyNames.contains(type.getName()) && module.getScope().hasType(type)) {
return true;
}
return symbol != null && onlyNames.contains(symbol);
}
}

private boolean isValidType(Type type) {
if (typesOnlyNames == null) return true;
return typesOnlyNames.contains(type.getName()) && module.getScope().hasType(type);
return isValidTypeOrSymbol(type, null);
}

public Function getExportedMethod(Type type, String name) {
if (isValidType(type)) {
if (isValidTypeOrSymbol(type, name)) {
return module.getScope().getExportedMethod(type, name);
} else {
return null;
Expand All @@ -50,7 +65,7 @@ public Function getExportedConversion(Type target, Type source) {
}

public Function getMethodForType(Type type, String methodName) {
if (isValidType(type)) {
if (isValidTypeOrSymbol(type, methodName)) {
return module.getScope().getMethodForType(type, methodName);
} else {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,17 +412,20 @@ private[runtime] class IrToTruffle(
exportedModule: BindingsMap.ExportedModule
): ImportExportScope = {
val exportedRuntimeMod = exportedModule.module.module.unsafeAsModule()
new ImportExportScope(exportedRuntimeMod)
new ImportExportScope(exportedRuntimeMod, null, null)
}

override protected def buildImportScope(
resolvedImport: BindingsMap.ResolvedImport,
resolvedModule: ResolvedModule
): ImportExportScope = {
val mod = resolvedModule.module.unsafeAsModule()
resolvedImport.importDef.onlyNames
.map(only => new ImportExportScope(mod, only.map(_.name).asJava))
.getOrElse(new ImportExportScope(mod))
val d = resolvedImport.importDef
val onlyNamesOrNull =
d.onlyNames.map(only => only.map(_.name).asJava).getOrElse(null)
val hiddenNamesOrNull =
d.hiddenNames.map(hide => hide.map(_.name).asJava).getOrElse(null)
new ImportExportScope(mod, onlyNamesOrNull, hiddenNamesOrNull)
}
}

Expand Down
Loading