diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Dictionary.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Dictionary.enso index 2b1207790702..d9e6539ced7e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Dictionary.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Dictionary.enso @@ -174,9 +174,9 @@ type Dictionary key value insert self key=(Missing_Argument.throw "key") value=(Missing_Argument.throw "value") no_warning:Boolean=False = new_dict = self.insert_builtin key value case key of - _ : Float -> + key_as_float : Float -> if no_warning then new_dict else - Warning.attach (Floating_Point_Equality.Used_As_Dictionary_Key key) new_dict + Warning.attach (Floating_Point_Equality.Used_As_Dictionary_Key key_as_float) new_dict _ -> new_dict ## GROUP Selections diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Column.enso index cca7450ec1e1..77170f50cc84 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Column.enso @@ -38,6 +38,7 @@ from project.Errors import Conversion_Failure, Inexact_Type_Coercion, Invalid_Co from project.Internal.Column_Format import all from project.Internal.Java_Exports import make_string_builder from project.Internal.Storage import enso_to_java, java_to_enso +from project.Internal.Type_Refinements.Single_Value_Column import refine_with_single_value polyglot java import org.enso.base.Time_Utils polyglot java import org.enso.table.data.column.operation.cast.CastProblemAggregator @@ -87,7 +88,7 @@ type Column example_from_vector = Column.from_vector "My Column" [1, 2, 3, 4, 5] from_vector : Text -> Vector -> Auto | Value_Type -> Column ! Invalid_Value_Type - from_vector (name : Text) (items : Vector) (value_type : Auto | Value_Type = Auto) = + from_vector (name : Text) (items : Vector) (value_type : Auto | Value_Type = Auto) -> Column = ## If the type does not accept date-time-like values, we can skip the additional logic for polyglot conversions that would normally be used, which is quite costly - so if we can guarantee it is unnecessary, @@ -120,10 +121,12 @@ type Column case needs_polyglot_conversion of True -> Java_Column.fromItems name (enso_to_java_maybe items) expected_storage_type java_problem_aggregator False -> Java_Column.fromItemsNoDateConversion name items expected_storage_type java_problem_aggregator - result = Column.from_java_column java_column . throw_on_warning Conversion_Failure - result.catch Conversion_Failure error-> - if error.example_values.is_empty then result else - raise_invalid_value_type_error error.example_values.first + multi_result = Column.from_java_column java_column + result = Warning.throw_on_warning multi_result Conversion_Failure + if Meta.is_error result . not then result else + result.catch Conversion_Failure error-> + if error.example_values.is_empty then result else + raise_invalid_value_type_error error.example_values.first ## PRIVATE Creates a new column given a name and an internal Java storage. @@ -135,9 +138,9 @@ type Column ## PRIVATE Creates a new column given a Java Column object. - from_java_column : Java_Column -> Column - from_java_column java_column = - Column.Value java_column + from_java_column java_column:Java_Column -> Column = + column = Column.Value java_column + column |> refine_with_single_value ## PRIVATE ADVANCED @@ -1202,8 +1205,8 @@ type Column storage_type = Storage.from_value_type_strict common_type new_storage = Java_Problems.with_problem_aggregator Problem_Behavior.Report_Warning java_problem_aggregator-> case default of - Column.Value java_col -> - other_storage = java_col.getStorage + col : Column -> + other_storage = col.java_column.getStorage storage.fillMissingFrom other_storage storage_type java_problem_aggregator _ -> storage.fillMissing default storage_type java_problem_aggregator @@ -2699,9 +2702,9 @@ run_vectorized_binary_op column name operand new_name=Nothing fallback_fn=Nothin Java_Problems.with_map_operation_problem_aggregator column.name Problem_Behavior.Report_Warning problem_builder-> storage_type = resolve_storage_type expected_result_type case operand of - Column.Value col2 -> + operand_column : Column -> s1 = column.java_column.getStorage - s2 = col2.getStorage + s2 = operand_column.java_column.getStorage rs = Polyglot_Helpers.handle_polyglot_dataflow_errors <| s1.vectorizedOrFallbackZip name problem_builder fallback_fn s2 skip_nulls storage_type Column.from_storage effective_new_name rs @@ -2792,9 +2795,9 @@ run_vectorized_binary_op_with_fallback_problem_handling column name operand fall _ -> fallback_fn problem_builder storage_type = resolve_storage_type expected_result_type case operand of - Column.Value col2 -> + operand_column : Column -> s1 = column.java_column.getStorage - s2 = col2.getStorage + s2 = operand_column.java_column.getStorage rs = Polyglot_Helpers.handle_polyglot_dataflow_errors <| s1.vectorizedOrFallbackZip name problem_builder applied_fn s2 skip_nulls storage_type Column.from_storage new_name rs diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Single_Value_Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Single_Value_Column.enso new file mode 100644 index 000000000000..ef349b0f4d38 --- /dev/null +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Single_Value_Column.enso @@ -0,0 +1,34 @@ +private + +from Standard.Base import all + +import project.Column.Column +import project.Value_Type.Value_Type +from project.Internal.Type_Refinements.Single_Value_Column_Extensions import all + +refine_with_single_value (column : Column) = + ## We treat a column as single value if it contains a single not-nothing value. + if is_single_value column . not then column else case column.inferred_precise_value_type of + Value_Type.Integer _ -> + # `inferred_precise_value_type` will return Integer if the column was Float (or Mixed) but contained integral values - e.g. [2.0] + # We inspect the actual value to correctly deal with both Float and Mixed base type. + value = column.at 0 + case value of + # If the value was really a float, we preserve that. + _ : Float -> (column : Column & Float) + # Otherwise we treat it as an integer. + _ -> (column : Column & Integer) + Value_Type.Float _ -> (column : Column & Float) + Value_Type.Char _ _ -> (column : Column & Text) + Value_Type.Boolean -> (column : Column & Boolean) + Value_Type.Date -> (column : Column & Date) + Value_Type.Time -> (column : Column & Time_Of_Day) + Value_Type.Date_Time True -> (column : Column & Date_Time) + Value_Type.Decimal _ scale -> + is_integer = scale == 0 + if is_integer then (column : Column & Integer) else (column : Column & Decimal) + # Other types (e.g. Mixed) are not supported. + _ -> column + +is_single_value column:Column -> Boolean = + (column.length == 1) && (column.at 0 . is_nothing . not) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Single_Value_Column_Extensions.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Single_Value_Column_Extensions.enso new file mode 100644 index 000000000000..8ded7f7557d0 --- /dev/null +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Single_Value_Column_Extensions.enso @@ -0,0 +1,51 @@ +private + +from Standard.Base import all + +import project.Column.Column +from project.Internal.Type_Refinements.Single_Value_Column import is_single_value + +## This conversion is internal and should never be exported. +Integer.from (that : Column) -> Integer = + Runtime.assert (is_single_value that) + x = that.at 0 + case x of + _ : Integer -> x + _ : Float -> + Runtime.assert (x % 1.0 == 0.0) + x.truncate + +## This conversion is internal and should never be exported. +Float.from (that : Column) -> Float = + Runtime.assert (is_single_value that) + that.at 0 + +## This conversion is internal and should never be exported. +Text.from (that : Column) -> Text = + Runtime.assert (is_single_value that) + that.at 0 + +## This conversion is internal and should never be exported. +Boolean.from (that : Column) -> Boolean = + Runtime.assert (is_single_value that) + that.at 0 + +## This conversion is internal and should never be exported. +Date.from (that : Column) -> Date = + Runtime.assert (is_single_value that) + that.at 0 + +## This conversion is internal and should never be exported. +Time_Of_Day.from (that : Column) -> Time_Of_Day = + Runtime.assert (is_single_value that) + that.at 0 + +## This conversion is internal and should never be exported. +Date_Time.from (that : Column) -> Date_Time = + Runtime.assert (is_single_value that) + that.at 0 + +## This conversion is internal and should never be exported. +Decimal.from (that : Column) -> Decimal = + Runtime.assert (is_single_value that) + that.at 0 diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Table.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Table.enso index 8294aae14e89..2aaaf63716a2 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Table.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Table.enso @@ -144,13 +144,13 @@ type Table new : Vector (Vector | Column) -> Table new columns = invalid_input_shape = - Error.throw (Illegal_Argument.Error "Each column must be represented by a pair whose first element is the column name and the second element is a vector of elements that will constitute that column, or an existing column.") + Error.throw (Illegal_Argument.Error "Each column must be represented by a pair whose first element is the column name and the second element is a vector of elements that will constitute that column, or an existing column. Got: "+columns.to_text) cols = columns.map on_problems=No_Wrap.Value c-> case c of v : Vector -> if v.length != 2 then invalid_input_shape else Column.from_vector (v.at 0) (v.at 1) . java_column - Column.Value java_col -> java_col + col : Column -> col.java_column _ -> invalid_input_shape Panic.recover Illegal_Argument <| if cols.is_empty then @@ -2472,9 +2472,9 @@ type Table unique.mark_used self.column_names resolved = case value of + _ : Column -> value _ : Text -> self.make_constant_column value _ : Expression -> self.evaluate_expression value on_problems - _ : Column -> value _ : Constant_Column -> self.make_constant_column value.value _ : Simple_Expression -> value.evaluate self (set_mode==Set_Mode.Update && as=="") on_problems _ -> Error.throw (Illegal_Argument.Error "Unsupported type for `Table.set`.") diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java index a22787e14ea5..759637c1dc1f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java @@ -9,18 +9,18 @@ import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.data.Type; -import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; +import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; final class AllOfTypesCheckNode extends AbstractTypeCheckNode { @Children private AbstractTypeCheckNode[] checks; - @Child private TypesLibrary types; + @Child private TypeOfNode typeNode; @Child private EnsoMultiValue.NewNode newNode; AllOfTypesCheckNode(String name, AbstractTypeCheckNode[] checks) { super(name); this.checks = checks; - this.types = TypesLibrary.getFactory().createDispatched(checks.length); + this.typeNode = TypeOfNode.create(); this.newNode = EnsoMultiValue.NewNode.create(); } @@ -46,7 +46,8 @@ Object executeCheckOrConversion(VirtualFrame frame, Object value, ExpressionNode if (result == null) { return null; } - var t = types.getType(result); + var t = typeNode.findTypeOrNull(result); + assert t != null : "Value " + result + " doesn't have type!"; var ctx = EnsoContext.get(this); if (ctx.getBuiltins().number().getInteger() == t) { if (++integers > 1) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java index 3c84ecccedd2..afe2adbd7eb0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java @@ -26,9 +26,19 @@ final class EnsoMultiType { private final Type[] types; private EnsoMultiType(Type[] types) { + assert checkNonNull(types); this.types = types; } + private static boolean checkNonNull(Type[] types) { + for (var t : types) { + if (t == null) { + return false; + } + } + return true; + } + @CompilerDirectives.TruffleBoundary static EnsoMultiType findOrCreateSlow(Type[] types, int from, int to) { var mt = new EnsoMultiType(Arrays.copyOfRange(types, from, to)); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java index 93e11ef9e182..06257b80987e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java @@ -506,7 +506,9 @@ Object invokeMember( @Override public String toString() { var both = EnsoMultiType.AllTypesWith.getUncached().executeAllTypes(dispatch, extra, 0); - return Stream.of(both).map(t -> t.getName()).collect(Collectors.joining(" & ")); + return Stream.of(both) + .map(t -> t != null ? t.getName() : "[?]") + .collect(Collectors.joining(" & ")); } /** Casts {@link EnsoMultiValue} to requested type effectively. */ diff --git a/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java b/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java index bad43bb8aec9..5221576f113a 100644 --- a/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java +++ b/std-bits/table/src/main/java/org/enso/table/excel/ExcelConnectionPool.java @@ -201,11 +201,6 @@ private String getKeyForFile(File file) throws IOException { } void release(ReadOnlyExcelConnection excelConnection) throws IOException { - System.out.println("AAAo"); - System.err.println("AAAo"); - new Exception().printStackTrace(); - System.out.println("AAAo2"); - System.err.println("AAAo2"); synchronized (this) { excelConnection.record.refCount--; if (excelConnection.record.refCount <= 0) { diff --git a/test/Table_Tests/src/In_Memory/Main.enso b/test/Table_Tests/src/In_Memory/Main.enso index b2113f775741..29bcf2004ba1 100644 --- a/test/Table_Tests/src/In_Memory/Main.enso +++ b/test/Table_Tests/src/In_Memory/Main.enso @@ -12,6 +12,7 @@ import project.In_Memory.Fan_Out_Spec import project.In_Memory.Integer_Overflow_Spec import project.In_Memory.Lossy_Conversions_Spec import project.In_Memory.Parse_To_Table_Spec +import project.In_Memory.Single_Value_Column_Spec import project.In_Memory.Split_Tokenize_Spec import project.In_Memory.Table_Spec import project.In_Memory.Table_Xml_Spec @@ -33,6 +34,7 @@ add_specs suite_builder = Integer_Overflow_Spec.add_specs suite_builder Lossy_Conversions_Spec.add_specs suite_builder Parse_To_Table_Spec.add_specs suite_builder + Single_Value_Column_Spec.add_specs suite_builder Split_Tokenize_Spec.add_specs suite_builder Table_Conversion_Spec.add_specs suite_builder Table_Date_Spec.add_specs suite_builder diff --git a/test/Table_Tests/src/In_Memory/Single_Value_Column_Spec.enso b/test/Table_Tests/src/In_Memory/Single_Value_Column_Spec.enso new file mode 100644 index 000000000000..995ee3b7b042 --- /dev/null +++ b/test/Table_Tests/src/In_Memory/Single_Value_Column_Spec.enso @@ -0,0 +1,184 @@ +from Standard.Base import all +import Standard.Base.Errors.Common.No_Such_Method +import Standard.Base.Errors.Common.Type_Error + +from Standard.Table import Column, Table, Value_Type + +from Standard.Test import all + +add_specs suite_builder = + suite_builder.group "single-value Column" group_builder-> + group_builder.specify "should be a Column but also Integer" <| + c1 = Column.from_vector "ints" [1] + c1.should_be_a Column + (c1:Integer).should_equal 1 + Test.expect_panic Type_Error (c1:Text) + + # Also test with a large integer + c2 = Column.from_vector "bigger ints" [3^500] + c2.should_be_a Column + (c2:Integer).should_equal (3^500) + + group_builder.specify "should be a Column but also Float" <| + c1 = Column.from_vector "floats" [1.5] + c1.should_be_a Column + (c1:Float).should_equal 1.5 + # But a float is not an integer: + Test.expect_panic Type_Error (c1:Integer) + + # Integral float is still not an integer + c10 = Column.from_vector "c10" [1.0] + c10.should_be_a Column + (c10:Float).should_equal 1.0 + Test.expect_panic Type_Error (1.0 : Integer) + Test.expect_panic Type_Error (c10:Integer) + + group_builder.specify "should be a Column but also Text" <| + c1 = Column.from_vector "texts" ["a"] + c1.should_be_a Column + (c1:Text).should_equal "a" + Test.expect_panic Type_Error (c1:Integer) + + group_builder.specify "should be a Column but also Boolean" <| + c1 = Column.from_vector "bools" [True] + c1.should_be_a Column + (c1:Boolean).should_equal True + + group_builder.specify "should be a Column but also Date" <| + c1 = Column.from_vector "dates" [Date.new 2021 1 1] + c1.should_be_a Column + (c1:Date).should_equal (Date.new 2021 1 1) + + group_builder.specify "should be a Column but also Time_Of_Day" <| + c1 = Column.from_vector "times" [Time_Of_Day.new 12 0 0] + c1.should_be_a Column + (c1:Time_Of_Day).should_equal (Time_Of_Day.new 12 0 0) + + group_builder.specify "should be a Column but also Date_Time" <| + c1 = Column.from_vector "date times" [Date_Time.new 2021 1 1 12 0 0] + c1.should_be_a Column + (c1:Date_Time).should_equal (Date_Time.new 2021 1 1 12 0 0) + + group_builder.specify "should be a Column but also Decimal" <| + c1 = Column.from_vector "decimals" [Decimal.new "3.5"] + c1.should_be_a Column + (c1:Decimal).should_equal (Decimal.new "3.5") + + group_builder.specify "does not apply to columns that have more rows" <| + c1 = Column.from_vector "more rows" [1, 2] + c1.should_be_a Column + Test.expect_panic Type_Error (c1:Integer) + + group_builder.specify "nothing column is not a single Nothing value" <| + c = Column.from_vector "n" [Nothing] + c.should_be_a Column + c.is_a Nothing . should_be_false + Test.expect_panic Type_Error (c:Nothing) + Test.expect_panic Type_Error (c:Integer) + Test.expect_panic Type_Error (c:Text) + + group_builder.specify "also works for Mixed columns" <| + c1 = Column.from_vector "mixed int" [23] Value_Type.Mixed + c1.value_type.should_equal Value_Type.Mixed + (c1:Integer).should_equal 23 + Test.expect_panic Type_Error (c1:Float) + + c2 = Column.from_vector "mixed float" [42.0] Value_Type.Mixed + c2.value_type.should_equal Value_Type.Mixed + (c2:Float).should_equal 42.0 + Test.expect_panic Type_Error (c2:Integer) + + c3 = Column.from_vector "mixed text" ["hello"] Value_Type.Mixed + c3.value_type.should_equal Value_Type.Mixed + (c3:Text).should_equal "hello" + + group_builder.specify "should still allow all column operations and preserve its single-value-ness" <| + c1 = Column.from_vector "c1" [23] + c1.length . should_equal 1 + c1.count_nothing . should_equal 0 + c1.value_type . should_equal Value_Type.Integer + c1.at 0 . should_equal 23 + + c2 = c1 + 100 + c2.should_be_a Column + (c2:Integer).should_equal 123 + + group_builder.specify "should also be accepted as right-hand side of binary operators (if casted) and return just a number" <| + c1 = Column.from_vector "c1" [23] + r1 = 100 + c1:Integer + r1.is_a Column . should_be_false + r1 . should_equal 123 + + r2 = (2 * c1:Integer) + 100 + r2 . should_equal 146 + + group_builder.specify "will also allow to call methods of the single value (if casted)" <| + c1 = Column.from_vector "c1" [1000] + # Assuming Column has no log function + Test.expect_panic No_Such_Method (c1.log 10) + r1 = (c1:Number).log 10 + r1 . should_equal 3 epsilon=0.001 + + c2 = Column.from_vector "c2" [Date.new 2025 1 1] + Test.expect_panic No_Such_Method (c2.add_work_days 100) + (c2:Date).add_work_days 100 . should_equal (Date.new 2025 5 21) + + my_integer_fn (x : Integer) -> Integer = + 1 + x*2 + my_text_fn (x : Text) -> Text = + x + "!" + group_builder.specify "can be passed to functions that expect a matching single value (if explicitly casted)" <| + c1 = Column.from_vector "c1" [10] + # Without cast it is a panic + Test.expect_panic Type_Error (my_integer_fn c1) + # But passes with cast + r1 = my_integer_fn (c1:Integer) + r1 . should_equal 21 + r1.should_be_a Integer + # The value is no longer a column after passing it to a function + r1 . is_a Column . should_be_false + + c2 = Column.from_vector "c2" ["hello"] + Test.expect_panic Type_Error (my_text_fn c2) + r2 = my_text_fn c2:Text + r2 . should_equal "hello!" + + group_builder.specify "retain the properties after being transformed using column operations" <| + c1 = Column.from_vector "c1" [10] + c2 = Column.from_vector "c2" ["hello"] + + c3 = c1.zip c2 a-> b-> a.to_text+" "+b + c3.should_be_a Column + r3 = my_text_fn c3:Text + r3 . should_equal "10 hello!" + + c4 = c1.is_nothing + c4.should_be_a Column + (c4:Boolean).should_equal False + + (c1.is_present : Boolean).should_equal True + (c2.text_length : Integer).should_equal 5 + (c2.text_left 1 : Text).should_equal "h" + (c2.is_in [] : Boolean).should_equal False + + c5 = Column.from_vector "c5" [Date.new 2024 1 2] + (c5.day : Integer).should_equal 2 + (c5.year : Integer).should_equal 2024 + + group_builder.specify "should also work for columns extracted from a Table" <| + table = Table.new [["a", [42]], ["b", ["hello"]]] + c1 = table.at "a" + c1.should_be_a Column + (c1:Integer).should_equal 42 + Test.expect_panic Type_Error (my_integer_fn c1) + my_integer_fn c1:Integer . should_equal 85 + + Test.expect_panic Type_Error <| + my_text_fn (table.at "b") + my_text_fn (table.at "b" : Text) . should_equal "hello!" + + +main filter=Nothing = + suite = Test.build suite_builder-> + add_specs suite_builder + suite.run_with_filter filter