From 34ddd37301e242f8d73eec8d06f732561e30a9e0 Mon Sep 17 00:00:00 2001 From: AdRiley Date: Tue, 17 Dec 2024 13:19:07 +0000 Subject: [PATCH 01/19] Add Missing_Argument error to join right (#11888) --- app/gui/src/project-view/util/callTree.ts | 8 ++++++-- .../lib/Standard/Database/0.0.0-dev/src/DB_Table.enso | 3 ++- distribution/lib/Standard/Table/0.0.0-dev/src/Table.enso | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/gui/src/project-view/util/callTree.ts b/app/gui/src/project-view/util/callTree.ts index 5c34e4cf584f..087ded4b9163 100644 --- a/app/gui/src/project-view/util/callTree.ts +++ b/app/gui/src/project-view/util/callTree.ts @@ -4,7 +4,11 @@ import type { WidgetConfiguration } from '@/providers/widgetRegistry/configurati import * as widgetCfg from '@/providers/widgetRegistry/configuration' import { DisplayMode } from '@/providers/widgetRegistry/configuration' import type { MethodCallInfo } from '@/stores/graph/graphDatabase' -import type { SuggestionEntry, SuggestionEntryArgument } from '@/stores/suggestionDatabase/entry' +import { + isRequiredArgument, + type SuggestionEntry, + type SuggestionEntryArgument, +} from '@/stores/suggestionDatabase/entry' import { Ast } from '@/util/ast' import type { AstId } from '@/util/ast/abstract' import { findLastIndex, tryGetIndex } from '@/util/data/array' @@ -118,7 +122,7 @@ export class ArgumentPlaceholder extends Argument { /** TODO: Add docs */ override get hideByDefault(): boolean { - return this.argInfo.hasDefault && this.dynamicConfig?.display !== DisplayMode.Always + return !isRequiredArgument(this.argInfo) && this.dynamicConfig?.display !== DisplayMode.Always } } diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/DB_Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/DB_Table.enso index 4d41918bd873..25da867e82e0 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/DB_Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/DB_Table.enso @@ -9,6 +9,7 @@ import Standard.Base.Errors.Common.Floating_Point_Equality import Standard.Base.Errors.Common.Incomparable_Values import Standard.Base.Errors.Common.Index_Out_Of_Bounds import Standard.Base.Errors.Common.Type_Error +import Standard.Base.Errors.Common.Missing_Argument import Standard.Base.Errors.Deprecated.Deprecated import Standard.Base.Errors.File_Error.File_Error import Standard.Base.Errors.Illegal_Argument.Illegal_Argument @@ -1495,7 +1496,7 @@ type DB_Table @join_kind Widget_Helpers.make_join_kind_selector @on Widget_Helpers.make_join_condition_selector join : DB_Table -> Join_Kind -> Join_Condition | Text | Vector (Join_Condition | Text) -> Text -> Problem_Behavior -> DB_Table - join self right (join_kind : Join_Kind = ..Left_Outer) (on : Join_Condition | Text | Vector (Join_Condition | Text) = (default_join_condition self join_kind)) (right_prefix:Text="Right ") (on_problems:Problem_Behavior=..Report_Warning) = + join self right=(Missing_Argument.throw "right") (join_kind : Join_Kind = ..Left_Outer) (on : Join_Condition | Text | Vector (Join_Condition | Text) = (default_join_condition self join_kind)) (right_prefix:Text="Right ") (on_problems:Problem_Behavior=..Report_Warning) = Feature.Join.if_supported_else_throw self.connection.dialect "join" <| self.join_or_cross_join right join_kind on right_prefix on_problems 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 641da532d9b1..c7147eb87b4a 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 @@ -8,6 +8,7 @@ import Standard.Base.Errors.Common.Additional_Warnings import Standard.Base.Errors.Common.Floating_Point_Equality import Standard.Base.Errors.Common.Incomparable_Values import Standard.Base.Errors.Common.Index_Out_Of_Bounds +import Standard.Base.Errors.Common.Missing_Argument import Standard.Base.Errors.Common.No_Such_Method import Standard.Base.Errors.Common.Out_Of_Memory import Standard.Base.Errors.Common.Type_Error @@ -2665,7 +2666,7 @@ type Table @join_kind Widget_Helpers.make_join_kind_selector @on Widget_Helpers.make_join_condition_selector join : Table -> Join_Kind -> Vector (Join_Condition | Text) | Text -> Text -> Problem_Behavior -> Table - join self right:Table (join_kind : Join_Kind = ..Left_Outer) on=[Join_Condition.Equals self.column_names.first] right_prefix:Text="Right " on_problems:Problem_Behavior=..Report_Warning = Out_Of_Memory.handle_java_exception "join" <| + join self right:Table=(Missing_Argument.throw "right") (join_kind : Join_Kind = ..Left_Outer) on=[Join_Condition.Equals self.column_names.first] right_prefix:Text="Right " on_problems:Problem_Behavior=..Report_Warning = Out_Of_Memory.handle_java_exception "join" <| columns_to_keep = case join_kind of Join_Kind.Left_Exclusive -> [True, False] Join_Kind.Right_Exclusive -> [False, True] From 1df9ea6f3b494afdb79c1125e0dd2e8d6ad737eb Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Dec 2024 14:33:07 +0100 Subject: [PATCH 02/19] raiseAssertionPanic instead of shouldNotReachHere (#11877) --- distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso | 3 +-- .../enso/interpreter/runtime/warning/SetWarningsNode.java | 7 ++----- test/Base_Tests/src/Semantic/Warnings_Spec.enso | 5 +++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso index b2c8ffd9ad70..1f21a194a7ea 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso @@ -90,8 +90,7 @@ type Warning Arguments: - value: the value to which warnings should be set to. - warnings: vector of warnings to set to the value. - set : Any -> Vector Warning -> Any - set value warnings = set_array value warnings + set value (warnings:Vector Warning) = set_array value warnings ## PRIVATE ADVANCED diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/warning/SetWarningsNode.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/warning/SetWarningsNode.java index b6653e76ed88..eddb8f9ae086 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/warning/SetWarningsNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/warning/SetWarningsNode.java @@ -1,6 +1,5 @@ package org.enso.interpreter.runtime.warning; -import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateUncached; @@ -91,9 +90,7 @@ Object doSetInteropArray( } else { return new WithWarnings(object, maxWarns, isLimitReached, warnMap); } - } catch (UnsupportedMessageException | InvalidArrayIndexException e) { - throw CompilerDirectives.shouldNotReachHere(e); - } catch (ClassCastException e) { + } catch (ClassCastException | UnsupportedMessageException | InvalidArrayIndexException e) { throw ctx.raiseAssertionPanic(this, "Expected Warning, got something else", e); } } @@ -106,7 +103,7 @@ protected static boolean isEmpty(Object warns, InteropLibrary interop) { try { return interop.getArraySize(warns) == 0; } catch (UnsupportedMessageException e) { - throw CompilerDirectives.shouldNotReachHere(e); + return false; } } return false; diff --git a/test/Base_Tests/src/Semantic/Warnings_Spec.enso b/test/Base_Tests/src/Semantic/Warnings_Spec.enso index aca9c4778eac..7e9969f1ca4b 100644 --- a/test/Base_Tests/src/Semantic/Warnings_Spec.enso +++ b/test/Base_Tests/src/Semantic/Warnings_Spec.enso @@ -451,6 +451,11 @@ add_specs suite_builder = suite_builder.group "Dataflow Warnings" group_builder- res2 . should_equal True Warning.get_all res2 . map .value . should_equal ["WARNING2"] + group_builder.specify "warning & panic" <| + panic_text = Panic.catch Standard.Base.Errors.Common.Type_Error (Warning.set 1 2) caught_panic-> + caught_panic.payload.to_text + panic_text . should_equal "Type error: expected `warnings` to be Vector, but got Integer." + main filter=Nothing = suite = Test.build suite_builder-> add_specs suite_builder From cf82c8c3c743e4327b97747a77d2d0c3d9a6dda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Grabarz?= Date: Tue, 17 Dec 2024 14:36:32 +0100 Subject: [PATCH 03/19] Enable prettier on docs markdown (#11875) * Enable prettier on docs markdown * Run prettier always --------- Co-authored-by: Sergei Garin --- .github/workflows/gui-checks.yml | 26 ++-- .github/workflows/gui-pull-request.yml | 46 ++++++ .prettierignore | 1 - docs/semantics/errors.md | 14 +- docs/syntax/README.md | 3 +- docs/syntax/conversions.md | 28 ++-- docs/types/README.md | 3 +- docs/types/access-modifiers.md | 59 ++++---- docs/types/contexts.md | 80 ++++++----- docs/types/dynamic-dispatch.md | 54 +++---- docs/types/errors.md | 21 ++- docs/types/evaluation.md | 33 ++--- docs/types/function-types.md | 41 +++--- docs/types/goals.md | 3 +- docs/types/hierarchy.md | 32 ++--- docs/types/inference-and-checking.md | 16 +-- docs/types/intersection-types.md | 181 ++++++++++++++---------- docs/types/modules.md | 15 +- docs/types/parallelism.md | 6 +- docs/types/pattern-matching.md | 3 +- docs/types/type-directed-programming.md | 15 +- 21 files changed, 361 insertions(+), 319 deletions(-) diff --git a/.github/workflows/gui-checks.yml b/.github/workflows/gui-checks.yml index 5d1091df8457..97f6f3c12393 100644 --- a/.github/workflows/gui-checks.yml +++ b/.github/workflows/gui-checks.yml @@ -31,17 +31,6 @@ jobs: node-version-file: .node-version cache: "pnpm" - - uses: actions/cache/restore@v4 - name: Download cache - id: cache - with: - path: | - **/.eslintcache - node_modules/.cache/prettier - key: ${{ runner.os }}-gui-${{ github.run_id }} - restore-keys: | - ${{ runner.os }}-gui - - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') name: Installing wasm-pack uses: jetli/wasm-pack-action@v0.4.0 @@ -51,10 +40,15 @@ jobs: - name: ๐Ÿ“ฆ Install dependencies run: pnpm install --frozen-lockfile - - name: ๐Ÿ“ Prettier - id: prettier - continue-on-error: true - run: pnpm run ci:prettier + - uses: actions/cache/restore@v4 + name: Download cache + id: cache + with: + path: | + **/.eslintcache + key: ${{ runner.os }}-gui-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-gui # Next Tasks are depend on Typecheck, because we build libraries at this stage - name: ๐Ÿง  Typecheck @@ -88,7 +82,6 @@ jobs: - name: โŒ Fail if any check failed if: always() && (steps.prettier.outcome == 'failure' || steps.lint.outcome == 'failure' || steps.typecheck.outcome == 'failure' || steps.unit-tests.outcome == 'failure') run: | - echo "Prettier outcome: ${{ steps.prettier.outcome }}" echo "Lint outcome: ${{ steps.lint.outcome }}" echo "Typecheck outcome: ${{ steps.typecheck.outcome }}" echo "Unit tests outcome: ${{ steps.unit-tests.outcome }}" @@ -102,7 +95,6 @@ jobs: key: ${{ steps.cache.outputs.cache-primary-key }} path: | **/.eslintcache - node_modules/.cache/prettier playwright: name: ๐ŸŽญ Playwright Tests diff --git a/.github/workflows/gui-pull-request.yml b/.github/workflows/gui-pull-request.yml index 650f001d2279..e388a90ca524 100644 --- a/.github/workflows/gui-pull-request.yml +++ b/.github/workflows/gui-pull-request.yml @@ -58,6 +58,52 @@ jobs: echo "$file was changed" done + prettier: + name: ๐Ÿงน Prettier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + name: โŽ” Setup Node + with: + node-version-file: .node-version + cache: "pnpm" + + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.4.0 + with: + version: v0.12.1 + + - name: ๐Ÿ“ฆ Install dependencies + run: pnpm install --frozen-lockfile --ignore-scripts + + - uses: actions/cache/restore@v4 + name: Download cache + id: cache + with: + path: | + node_modules/.cache/prettier + key: ${{ runner.os }}-gui-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-gui + + - name: Run prettier + run: pnpm run ci:prettier + + - name: ๐Ÿ’พ Save cache + uses: actions/cache/save@v4 + if: always() && steps.cache.outputs.cache-hit != 'true' + id: save-cache + with: + key: ${{ steps.cache.outputs.cache-primary-key }} + path: | + node_modules/.cache/prettier + checks: name: ๐Ÿงฐ Checks uses: ./.github/workflows/gui-checks.yml diff --git a/.prettierignore b/.prettierignore index 709537c7f4b2..41d9adc6f8bc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -16,7 +16,6 @@ distribution/lib/Standard/*/*/manifest.yaml distribution/lib/Standard/*/*/polyglot distribution/lib/Standard/*/*/THIRD-PARTY distribution/docs-js -docs/**/*.md built-distribution/ THIRD-PARTY diff --git a/docs/semantics/errors.md b/docs/semantics/errors.md index 42ca7918cef5..0a64904fd669 100644 --- a/docs/semantics/errors.md +++ b/docs/semantics/errors.md @@ -20,8 +20,7 @@ users to deal with errors at runtime in the language. ## Asynchronous Exceptions -> [!WARNING] -> The actionables for this section are: +> [!WARNING] The actionables for this section are: > > - Why do we call it asynchronous, when they are synchronous!? > - Specify the semantics of Enso's async exceptions. @@ -32,19 +31,16 @@ There is a special [dynamic dispatch](../types/dynamic-dispatch.md) for `Error` values. A dataflow error dispatch first checks if it may call a method on `Error` type. -> [!WARNING] -> The actionables for this section are: +> [!WARNING] The actionables for this section are: > > - Specify the semantics of Enso's broken values. ## Warnings -> [!WARNING] -> TODO +> [!WARNING] TODO > > - Values in Enso may have warnings attached There is a special [dynamic dispatch](../types/dynamic-dispatch.md) for _values -with warnings_. Before we pass the dispatch to the underlying value, -we check if warning has a method 'overridden' - this is used for `remove_warnings`, -etc. +with warnings_. Before we pass the dispatch to the underlying value, we check if +warning has a method 'overridden' - this is used for `remove_warnings`, etc. diff --git a/docs/syntax/README.md b/docs/syntax/README.md index a933dfdf370f..999e37169156 100644 --- a/docs/syntax/README.md +++ b/docs/syntax/README.md @@ -43,7 +43,8 @@ The various components of Enso's syntax are described below: - [**Functions:**](./functions.md) The syntax for writing functions in Enso. - [**Function Arguments:**](./function-arguments.md) The syntax for function arguments in Enso. -- [**Conversions:**](./conversions.md) The syntax of special _conversion functions_ in Enso. +- [**Conversions:**](./conversions.md) The syntax of special _conversion + functions_ in Enso. - [**Field Access:**](./projections.md) The syntax for working with fields of data types in Enso. - [**Comments:**](./comments.md) The syntax for writing comments in Enso. diff --git a/docs/syntax/conversions.md b/docs/syntax/conversions.md index 2a3a2881daa0..2880c1a992a9 100644 --- a/docs/syntax/conversions.md +++ b/docs/syntax/conversions.md @@ -8,28 +8,36 @@ order: 10 # Conversions -Conversions are special [functions](./functions.md) associated with a [type](../types/hierarchy.md), -named `from` and taking single `that` argument. Following example: +Conversions are special [functions](./functions.md) associated with a +[type](../types/hierarchy.md), named `from` and taking single `that` argument. +Following example: + ```ruby type Complex Num re:Float im:Float Complex.from (that:Float) = Complex.Num that 0 ``` -defines type `Complex` and a **conversion** from `Float` which uses the provided `Float` value as -real part of a complex number while setting the imaginary part to zero. + +defines type `Complex` and a **conversion** from `Float` which uses the provided +`Float` value as real part of a complex number while setting the imaginary part +to zero. ## Type Checks -Conversions are integral part of [type checking](../types/inference-and-checking.md#type-checking-algorithm) +Conversions are integral part of +[type checking](../types/inference-and-checking.md#type-checking-algorithm) during runtime. Having the right conversion in scope one can write: + ```ruby complex_pi = 3.14:Complex ``` -to create a new instance of type `Complex`. The Enso runtime represents -the `3.14` literal as `Float` value and that would fail the `Complex` type check if there was no -conversion method available. However as there is one, the runtime uses `Complex.from` behind the -scene and `complex_pi` then becomes `Complex.Num 3.14 0` value. -Type checks may perform no, one or multiple conversions (like in case of +to create a new instance of type `Complex`. The Enso runtime represents the +`3.14` literal as `Float` value and that would fail the `Complex` type check if +there was no conversion method available. However as there is one, the runtime +uses `Complex.from` behind the scene and `complex_pi` then becomes +`Complex.Num 3.14 0` value. + +Type checks may perform no, one or multiple conversions (like in case of [intersection types](../types/intersection-types.md)). diff --git a/docs/types/README.md b/docs/types/README.md index 1b8961ec3369..62e0771b1a94 100644 --- a/docs/types/README.md +++ b/docs/types/README.md @@ -39,8 +39,7 @@ instrumental for ensuring that we build the right language. > described in the [syntax](../syntax/README.md) document makes no promises as > to whether said syntax will be exposed in the surface language. -> [!WARNING] -> The specification in this section is very outdated and far from +> [!WARNING] The specification in this section is very outdated and far from > reality. Sections that are known to be _"off"_ are marked as _warning_. Information on the type system is broken up into the following sections: diff --git a/docs/types/access-modifiers.md b/docs/types/access-modifiers.md index 9102146c0ff7..fcde50cde457 100644 --- a/docs/types/access-modifiers.md +++ b/docs/types/access-modifiers.md @@ -8,23 +8,23 @@ order: 4 # Access Modifiers -> [!WARNING] -> Everybody who ever maintained a large system knows [encapsulation is essential](../semantics/encapsulation.md). +> [!WARNING] Everybody who ever maintained a large system knows +> [encapsulation is essential](../semantics/encapsulation.md). > -> While we don't usually like making things private in a programming language, it -> sometimes the case that it is necessary to indicate that certain fields should -> not be touched (as this might break invariants and such like). To this end, Enso -> provides an explicit mechanism for access modification. +> While we don't usually like making things private in a programming language, +> it sometimes the case that it is necessary to indicate that certain fields +> should not be touched (as this might break invariants and such like). To this +> end, Enso provides an explicit mechanism for access modification. Enso targets large user base of _non-programmers_. They are mostly focused on -getting their job done and [encapsulation](../semantics/encapsulation.md) of their own code is the last thing -that comes to their mind. +getting their job done and [encapsulation](../semantics/encapsulation.md) of +their own code is the last thing that comes to their mind. -On the other hand, Enso supports and encourages creation of *sharable libraries*. -Maintainers of such libraries are likely to treat API design and its -backward compatibility seriously. As such they need a way to [encapsulate](../semantics/encapsulation.md) -internals of their libraries and clearly *differentiate public API* and -implementations details. +On the other hand, Enso supports and encourages creation of _sharable +libraries_. Maintainers of such libraries are likely to treat API design and its +backward compatibility seriously. As such they need a way to +[encapsulate](../semantics/encapsulation.md) internals of their libraries and +clearly _differentiate public API_ and implementations details. @@ -35,27 +35,26 @@ implementations details. ## Access Modification -*By default* Enso elements (functions, types, methods) are *public*. -One has to use an access modifier to hide and [encapsulate](../semantics/encapsulation.md) them. The -reasoning is: those who don't care can access everything they create without -any restriction. Those who care can make things `private` with an additional -effort. +_By default_ Enso elements (functions, types, methods) are _public_. One has to +use an access modifier to hide and [encapsulate](../semantics/encapsulation.md) +them. The reasoning is: those who don't care can access everything they create +without any restriction. Those who care can make things `private` with an +additional effort. +Accessing any member under an access modifier is an error when performed from +another project. Such a check is enforced during runtime. -Accessing any member under an access modifier is an error when performed from another project. -Such a check is enforced during runtime. - -There is a command line switch to _disable access modifier check_. -It maybe be useful for experimentation. However the general suggestion is: -Avoid using it in production. +There is a command line switch to _disable access modifier check_. It maybe be +useful for experimentation. However the general suggestion is: Avoid using it in +production. ## Private Encapsulation is an effective _communication mechanism_ among _distributed -groups_ of developers. The `private` modifier hides implementation details from clients of the API. -The primary groups in the Enso case are those who *publish a library* -and those who *consume such a library*. +groups_ of developers. The `private` modifier hides implementation details from +clients of the API. The primary groups in the Enso case are those who _publish a +library_ and those who _consume such a library_. -As such Enso supports _library private_ encapsulation. -To hide any element (module, type, constructor, function) away -from *library consumers* prefix such an element with `private` keyword. +As such Enso supports _library private_ encapsulation. To hide any element +(module, type, constructor, function) away from _library consumers_ prefix such +an element with `private` keyword. diff --git a/docs/types/contexts.md b/docs/types/contexts.md index bccd3eeac918..e027139de2d2 100644 --- a/docs/types/contexts.md +++ b/docs/types/contexts.md @@ -8,21 +8,20 @@ order: 8 # Monadic Contexts -> [!WARNING] -> Reword for people without Haskell background who don't know what _lifting_ is. +> [!WARNING] Reword for people without Haskell background who don't know what +> _lifting_ is. > > Coming from a Haskell background, we have found that Monads provide a great > abstraction with which to reason about program behaviour, but they have some -> severe usability issues. The main one of these is the lack of automatic lifting, -> requiring users to explicitly lift computations through their monad transformer -> stack. +> severe usability issues. The main one of these is the lack of automatic +> lifting, requiring users to explicitly lift computations through their monad +> transformer stack. For a language as focused on usability as Enso is importing all the _complexity -of Haskell monads_ really isn't feasible. To -that end, we have created the notion of a 'Monadic Context', which is a monad -transformer based on Supermonads (see -[references](./references.md#monadic-contexts)). These have special support in -the compiler, and hence can be automatically lifted to aid usability. +of Haskell monads_ really isn't feasible. To that end, we have created the +notion of a 'Monadic Context', which is a monad transformer based on Supermonads +(see [references](./references.md#monadic-contexts)). These have special support +in the compiler, and hence can be automatically lifted to aid usability. > The actionables for this section are: > @@ -45,63 +44,68 @@ the compiler, and hence can be automatically lifted to aid usability. ## Context Syntax -> [!WARNING] -> There used to be three main notes about the syntax of contexts: +> [!WARNING] There used to be three main notes about the syntax of contexts: > > 1. Monadic contexts are defined using the `in` keyword (e.g. `Int in IO`). > 2. We have a symbol `!`, which is short-hand for putting something into the -> `Exception` monadic context. This is related to broken values. +> `Exception` monadic context. This is related to broken values. > 3. Contexts can be combined by using the standard typeset operators, or nested -> through repeated uses of `in`. - -There is no special syntax for contexts anymore. -Since [#3828](https://github.com/enso-org/enso/pull/3828) -Enso is no longer relaying on a haskelly solution. -Rather than that _contexts_ are being manupulated by -_standard library_ functions grouped around -`Standard.Base.Runtime.Context` & co. +> through repeated uses of `in`. + +There is no special syntax for contexts anymore. Since +[#3828](https://github.com/enso-org/enso/pull/3828) Enso is no longer relaying +on a haskelly solution. Rather than that _contexts_ are being manupulated by +_standard library_ functions grouped around `Standard.Base.Runtime.Context` & +co. + ```ruby Runtime.Context.Output.with_enabled <| File.new "c:\trash.txt" . delete ``` There is still the `!` symbol signaling [presence of errors](./errors.md) -- e.g. _broken values_. However the runtime can handle _broken values_ -even without presence of these _exception type signatures_. Thus the -compiler only verifies the referenced types are valid. + +- e.g. _broken values_. However the runtime can handle _broken values_ even + without presence of these _exception type signatures_. Thus the compiler only + verifies the referenced types are valid. ## Monadic Bind -> [!WARNING] -> Who knows what `<-` means in Haskell? -> +> [!WARNING] Who knows what `<-` means in Haskell? +> > It is also important to note that Enso has no equivalent to `<-` in Haskell. -> Instead, pure computations are implicitly placed in the `Pure` monadic context, -> and `=` acts to 'peel off' the outermost layer of contexts. As such, this means -> that `=` _always_ acts as `bind`, greatly simplifying how the type-checker has -> to work. +> Instead, pure computations are implicitly placed in the `Pure` monadic +> context, and `=` acts to 'peel off' the outermost layer of contexts. As such, +> this means that `=` _always_ acts as `bind`, greatly simplifying how the +> type-checker has to work. ## Inbuilt Contexts -Enso standard library defines `Input`, `Output` and `Dataflow_Stack_Trace` +Enso standard library defines `Input`, `Output` and `Dataflow_Stack_Trace` contects as of Enso 2024.5.1 version. Users cannot define their own. ### State -The _state_ concept is implement by standard libraries with _no support in the type system_. +The _state_ concept is implement by standard libraries with _no support in the +type system_. + +State acts as a +[thread local](https://en.wikipedia.org/wiki/Thread-local_storage) variable of +operating system: -State acts as a [thread local](https://en.wikipedia.org/wiki/Thread-local_storage) variable -of operating system: + - an _initializing code_ can set `State` up - execute some code -- a code somewhere deep the stack (while _initializing code_ is still on the stack) +- a code somewhere deep the stack (while _initializing code_ is still on the + stack) - may pick the state up - once the _initializing code_ finishes execution - the state is gone -It is an example of _tunnelling a value_ from one side (e.g. code) of the "tunnel" to another, -without the "tunnel" (e.g. thee code in between) knowing about it. +It is an example of _tunnelling a value_ from one side (e.g. code) of the +"tunnel" to another, without the "tunnel" (e.g. thee code in between) knowing +about it. @@ -51,11 +49,10 @@ the already evaluated value. ## Specifying Suspension in the Type System -> [!WARNING] -> The actionables for this section are: +> [!WARNING] The actionables for this section are: > > - Actually specify how the type system interacts with eager and lazy > evaluation. -Just use `~` to mark lazily computed function or atom arguments. The rest is handled -by the Enso runtime system. +Just use `~` to mark lazily computed function or atom arguments. The rest is +handled by the Enso runtime system. diff --git a/docs/types/function-types.md b/docs/types/function-types.md index 0d06a187eaab..e95f8237393e 100644 --- a/docs/types/function-types.md +++ b/docs/types/function-types.md @@ -11,8 +11,7 @@ order: 3 As a functional programming language, the type of functions in Enso (`->`) is key. There are a few things that should be noted about functions in Enso. -> [!NOTE] -> The actionables for this section: +> [!NOTE] The actionables for this section: > > - Work out a full specification for function typing and behaviour. > - Calling a function with an upper-case letter instantiates all of its type @@ -55,8 +54,8 @@ lexical scoping. For a detailed discussion of scoping, please see > ## Structural Type Shorthand > > In Enso, we want to be able to write a type-signature that represents types in -> terms of the operations that take place on the input values. A classical example -> is `add`: +> terms of the operations that take place on the input values. A classical +> example is `add`: > > ```ruby > add : a -> b -> b + a @@ -69,8 +68,8 @@ lexical scoping. For a detailed discussion of scoping, please see > signature that can't work, but the return type, in combination with our > integrated `Convertible` mechanism gives us the tools to make it work. > - The return type is `a + b`. This is a shorthand expression for a detailed -> desugaring. The desugaring provided below is what the typechecker would infer -> based on such a signature. +> desugaring. The desugaring provided below is what the typechecker would +> infer based on such a signature. > > ```ruby > add : forall a b c d. ({+ : Convertible b c => a -> c -> d} <: a) => a -> b -> d @@ -80,23 +79,23 @@ lexical scoping. For a detailed discussion of scoping, please see > follows: > > 1. The expression `b + a` is syntactic sugar for a method call on a: `a.+ b`. -> 2. This means that there must be a `+` method on a that takes both an `a` and a -> `b`, with return-type unspecified as of yet: `+ : a -> b -> ?` +> 2. This means that there must be a `+` method on a that takes both an `a` and +> a `b`, with return-type unspecified as of yet: `+ : a -> b -> ?` > 3. However, as `Convertible` is built into the language, we have to consider -> that for `a.+ b` to work, the `+` method can actually take any type to which -> `b` converts. This introduces the constraint `Convertible b c`, and we get -> `+ : a -> c -> ?` +> that for `a.+ b` to work, the `+` method can actually take any type to +> which `b` converts. This introduces the constraint `Convertible b c`, and +> we get `+ : a -> c -> ?` > 4. The return type from a function need not be determined by its arguments, so -> hence in the general case we have to default to an unrestricted type variable -> giving `+ a -> c -> d`. -> 5. This method must exist on the type `a`, resulting in the constraint that the -> row `{+ : a -> c -> d} <: a` must conform to that interface. +> hence in the general case we have to default to an unrestricted type +> variable giving `+ a -> c -> d`. +> 5. This method must exist on the type `a`, resulting in the constraint that +> the row `{+ : a -> c -> d} <: a` must conform to that interface. > 6. We now know the return type of `a + b`, and can rewrite it in the signature > as `d`. > -> Please note that `a <: b` (which can be flipped as `:>`) means that `a` is a row -> that is a sub-row contained within the row `b`. The containment relation allows -> for the possibility that `a == b`. +> Please note that `a <: b` (which can be flipped as `:>`) means that `a` is a +> row that is a sub-row contained within the row `b`. The containment relation +> allows for the possibility that `a == b`. > > The inferred type for any function should, in general, match the given type > signature. Cases where this break down should only exist where the type @@ -115,8 +114,7 @@ all arguments have been applied. This operator is `>>` (and its backwards cousin arguments, and the result consumes `n` arguments, applies them to `f`, and then applies the result of that plus any additional arguments to `g`. -> [!WARNING] -> Enso does support `>>` as well as `<<` operators, but +> [!WARNING] Enso does support `>>` as well as `<<` operators, but > > ```ruby > computeCoeff = (+) >> (*5) @@ -134,8 +132,7 @@ applies the result of that plus any additional arguments to `g`. In addition, we have the operator `.`, which acts as standard forward function chaining in Enso, and its backwards chaining cousin `<|`. -> [!NOTE] -> The actionables from this section are: +> [!NOTE] The actionables from this section are: > > - Examples for the more advanced use-cases of `>>` to decide if the type > complexity is worth it. diff --git a/docs/types/goals.md b/docs/types/goals.md index 11b53d69b584..684049addfea 100644 --- a/docs/types/goals.md +++ b/docs/types/goals.md @@ -23,8 +23,7 @@ needs to be as unobtrusive as possible. The high-level goals for the Enso type system are as follows: -> [!WARNING] -> _Not a goal anymore_: Enso is a dynamic language. Static type +> [!WARNING] _Not a goal anymore_: Enso is a dynamic language. Static type > inference is _not needed for execution_. As such _static typing_ is an > optional component - more a _linter_ than essential part of the system. > diff --git a/docs/types/hierarchy.md b/docs/types/hierarchy.md index 20af64e05f0f..a1f96f21c102 100644 --- a/docs/types/hierarchy.md +++ b/docs/types/hierarchy.md @@ -14,8 +14,7 @@ most generic type is `Any`. If a value has no better (more specific) type, it has the type `Any`. All operations defined on type `Any` can be performed on any value in the system. -> [!WARNING] -> _Typeset theory is far from current state of affairs_: +> [!WARNING] _Typeset theory is far from current state of affairs_: > > Enso is a statically typed language based upon a theory of set-based typing, > what we call `typesets`. This is a novel approach, and it is key to our intent @@ -39,8 +38,7 @@ A value in Enso can have multiple different types attributed to it. It is possible to query/inspect these types during runtime and thus decide what operations are available for a particular value at hand. -> [!WARNING] -> _Probably not true in current system at all_ +> [!WARNING] > _Probably not true in current system at all_ > > A brief note on naming (for more, please see the > [naming syntax](../syntax/naming.md)): @@ -87,8 +85,7 @@ v:Maybe v.value:Text ``` -> [!WARNING] -> There are no _Typesets_ in Enso anymore +> [!WARNING] There are no _Typesets_ in Enso anymore > > Typesets in Enso are an entity unique to Enso's type system. They are a > fundamental recognition of types as 'sets of values' in Enso, and while they @@ -144,8 +141,7 @@ They are as follows: - **Intersection - `&`:** This operator creates a typeset that contains the members in the [intersection of its operands](./intersection-types.md). -> [!WARNING] -> These operators _don't seem to be supported_. There is no plan to +> [!WARNING] These operators _don't seem to be supported_. There is no plan to > support following operators now: > > - **Subsumption - `<:`:** This operator asserts that the left hand operand is @@ -160,8 +156,7 @@ For information on the syntactic usage of these operators, please see the section on [type operators](#../syntax/types.md#type-operators) in the syntax design documentation. -> [!NOTE] -> The actionables for this section are: +> [!NOTE] The actionables for this section are: > > - When necessary, we need to _explicitly formalise_ the semantics of all of > these operators. @@ -169,8 +164,7 @@ design documentation. > generative) types? > - Are `<:` and `:` equivalent in the surface syntax? -> [!WARNING] -> _Typeset Subsumption_ isn't relevant +> [!WARNING] > _Typeset Subsumption_ isn't relevant > > For two typesets `a` and `b`, `a` is said to be subsumed by `b` (written using > the notation `a <: b`) if the following hold recursively. This can be thought @@ -244,11 +238,11 @@ Historically interfaces used to be defined by _duck typing_. As Enso is a dynamic language, having two types with the same operations means they can be used interchangingly. -> [!NOTE] -> A work on [type classes](https://github.com/orgs/enso-org/discussions/11366) support is under way +> [!NOTE] A work on +> [type classes](https://github.com/orgs/enso-org/discussions/11366) support is +> under way -> [!WARNING] -> _Doesn't match reality:_ +> [!WARNING] _Doesn't match reality:_ > > Because typesets can be matched _structurally_, all typesets implicitly define > interfaces. A type `t` conforming to an interface `i` in Enso is as simple as @@ -305,8 +299,7 @@ _finalizers_. An expression `a : b` says that the expression denoted by `a` has the type denoted by the expression `b`. -> [!WARNING] -> No support for Scoping in Type Ascription +> [!WARNING] No support for Scoping in Type Ascription > > Enso intends to support some form of mutual scoping between the left and right > sides of the type ascription operator. This introduces some complexity into @@ -323,8 +316,7 @@ denoted by the expression `b`. > bindings in groups, and the desugaring needs to depend on combinations of > `>>=` and `fix`. -> [!WARNING] -> There are _no projections_ right now and none are planned +> [!WARNING] There are _no projections_ right now and none are planned > > In order to work efficiently with typesets, we need the ability to seamlessly > access and modify (immutably) their properties. In the context of our type diff --git a/docs/types/inference-and-checking.md b/docs/types/inference-and-checking.md index f2c877ab638e..d33d0e7eb16a 100644 --- a/docs/types/inference-and-checking.md +++ b/docs/types/inference-and-checking.md @@ -8,8 +8,8 @@ order: 13 # Inference and Checking -In spite of being dynamically-typed language, Enso is built with a sophisticated type checker -capable of reasoning about Enso typed system. However, a type +In spite of being dynamically-typed language, Enso is built with a sophisticated +type checker capable of reasoning about Enso typed system. However, a type checker on its own is quite useless. For Enso to truly be usable, it must also have a powerful type inference engine. @@ -43,8 +43,7 @@ In order to make Enso's type inference as helpful and friendly as possible to our users, we want the ability to infer the _maximal subset_ of the types that Enso can express. -> [!WARNING] -> The actionables for this section are: +> [!WARNING] The actionables for this section are: > > - How do we do inference for higher-rank and impredicative instantiations. > - How do we infer contexts, and how do we make that inference granular (e.g. @@ -59,21 +58,18 @@ Enso can express. ## Type Inference Algorithm -> [!WARNING] -> The actionables for this section are: +> [!WARNING] The actionables for this section are: > > - Specify the inference algorithm. ### Inferring Dependency -> [!WARNING] -> The actionables for this section are: +> [!WARNING] The actionables for this section are: > > - Specify how (if at all) we can infer dependent quantifiers. ## Type Checking Algorithm -> [!WARNING] -> The actionables for this section are: +> [!WARNING] The actionables for this section are: > > - Specify the type checking algorithm. diff --git a/docs/types/intersection-types.md b/docs/types/intersection-types.md index 919e138a0cc1..dd053d845709 100644 --- a/docs/types/intersection-types.md +++ b/docs/types/intersection-types.md @@ -8,50 +8,60 @@ order: 2 # Intersection Types -Intersection types play an important role in Enso [type hierarchy](./hierarchy.md) -and its visual representation. Having a value that can play _multiple roles_ -at once is essential for smooth _live programming_ manipulation of such a value. - -Intersections types are created with the use of [`&` operator](./hierarchy.md#typeset-operators). -In an attempt to represent `Complex` numbers (with real and imaginary component) -one may decide to create a type that is both `Complex` and `Float` when the -imaginary part is `0`: +Intersection types play an important role in Enso +[type hierarchy](./hierarchy.md) and its visual representation. Having a value +that can play _multiple roles_ at once is essential for smooth _live +programming_ manipulation of such a value. + +Intersections types are created with the use of +[`&` operator](./hierarchy.md#typeset-operators). In an attempt to represent +`Complex` numbers (with real and imaginary component) one may decide to create a +type that is both `Complex` and `Float` when the imaginary part is `0`: + ```ruby type Complex Num re:Float im:Float - - plain_or_both self = + + plain_or_both self = if self.im != 0 then self else both = self.re : Complex&Float both # value with both types: Complex and Float -``` +``` + Having a value with such _intersection type_ allows the IDE to offer operations available on all individual types. ## Creating -Creating a value of _intersection types_ is as simple as performing a type check: +Creating a value of _intersection types_ is as simple as performing a type +check: + ```ruby self : Complex&Float ``` -However such a _type check_ is closely associated with [conversions](../syntax/conversions.md). -If the value doesn't represent all the desired types yet, then the system looks -for [conversion methods](../syntax/conversions.md) being available in the scope like: + +However such a _type check_ is closely associated with +[conversions](../syntax/conversions.md). If the value doesn't represent all the +desired types yet, then the system looks for +[conversion methods](../syntax/conversions.md) being available in the scope +like: + ``` Complex.from (that:Float) = Complex.Num that 0 ``` + and uses them to create all the values the _intersection type_ represents. -> [!NOTE] -> Note that if a `Float.from (that:Complex)` conversion were available in the scope, -> any `Complex` instance would be convertible to `Float` regardless of how it was constructed. -> To ensure that such mix-ins are only available on values that opt-in to being -> an intersection type (like in the `Complex` example above where we include -> the `Float` mix-in only if `self.im == 0`), we need to ensure that the conversion -> used to create the intersection type is not available in the default -> conversion resolution scope. Thus it cannot be defined in the same module -> as `Complex` or `Float` types, but instead it should be defined in a separate -> module that is only imported in the place that will be constructing the multi-values. +> [!NOTE] Note that if a `Float.from (that:Complex)` conversion were available +> in the scope, any `Complex` instance would be convertible to `Float` +> regardless of how it was constructed. To ensure that such mix-ins are only +> available on values that opt-in to being an intersection type (like in the +> `Complex` example above where we include the `Float` mix-in only if +> `self.im == 0`), we need to ensure that the conversion used to create the +> intersection type is not available in the default conversion resolution scope. +> Thus it cannot be defined in the same module as `Complex` or `Float` types, +> but instead it should be defined in a separate module that is only imported in +> the place that will be constructing the multi-values. @@ -3151,7 +3153,8 @@ type RefactoringRenameProjectResult = null; #### Errors -None +- [`ProjectRenameFailed`](#projectrenamefailed) to signal that the project + rename operation has failed. ### `refactoring/renameSymbol` @@ -3231,6 +3234,8 @@ interface RefactoringRenameSymbolResult { operation was not able to apply generated edits. - [`RefactoringNotSupported`](#refactoringnotsupported) to signal that the refactoring of the given expression is not supported. +- [`DefinitionAlreadyExists`](#definitionalreadyexists) to signal that the + definition with the provided name already exists in scope. ### `refactoring/projectRenamed` @@ -5911,6 +5916,28 @@ Signals that the refactoring of the given expression is not supported. } ``` +### `ProjectRenameFailed` + +Signals that the project rename failed. + +```typescript +"error" : { + "code" : 9004, + "message" : "Project rename failed [, ]" +} +``` + +### `DefinitionAlreadyExists` + +Signals that the definition with the provided name already exists in the scope. + +```typescript +"error" : { + "code" : 9005, + "message" : "Definition [] already exists" +} +``` + ### `AiHttpError` Signals about an error during the processing of AI http respnse. diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala index f31f5d7fd6b4..566afe90388f 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala @@ -77,4 +77,7 @@ object RefactoringApi { case class ProjectRenameFailed(oldName: String, newName: String) extends Error(9004, s"Project rename failed [$oldName, $newName]") + case class DefinitionAlreadyExists(name: String) + extends Error(9005, s"Definition [$name] already exists") + } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RenameFailureMapper.scala b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RenameFailureMapper.scala index e6e2ec460784..a092c38b75fa 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RenameFailureMapper.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RenameFailureMapper.scala @@ -15,6 +15,9 @@ object RenameFailureMapper { case error: Api.SymbolRenameFailed.ExpressionNotFound => RefactoringApi.ExpressionNotFoundError(error.expressionId) + case error: Api.SymbolRenameFailed.DefinitionAlreadyExists => + RefactoringApi.DefinitionAlreadyExists(error.name) + case error: Api.SymbolRenameFailed.FailedToApplyEdits => RefactoringApi.FailedToApplyEdits(error.module) diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 47ad311a518d..9d9ab4ccab90 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -1330,11 +1330,18 @@ object Runtime { * * @param expressionId the id of expression */ - @named("symbolRenameFailedExpressionNotFound") final case class ExpressionNotFound(expressionId: ExpressionId) extends SymbolRenameFailed.Error + /** Signals that the definition with the provided name already exists in the scope. + * + * @param name the definition name + */ + @named("symbolRenameFailedDefinitionAlreadyExists") + final case class DefinitionAlreadyExists(name: String) + extends SymbolRenameFailed.Error + /** Signals that it was unable to apply edits to the current module contents. * * @param module the module name diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala index d2112a03aecd..a319085cbdf3 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala @@ -2,8 +2,9 @@ package org.enso.compiler.refactoring import org.enso.compiler.core.Implicits.AsMetadata import org.enso.compiler.core.{ExternalID, IR, Identifier} -import org.enso.compiler.core.ir.Name +import org.enso.compiler.core.ir.{Expression, Name} import org.enso.compiler.core.ir.expression.Application +import org.enso.compiler.core.ir.module.scope.definition.Method import org.enso.compiler.data.BindingsMap import org.enso.compiler.pass.analyse.DataflowAnalysis import org.enso.compiler.pass.resolve.MethodCalls @@ -31,6 +32,69 @@ trait IRUtils { None } + /** Find definitions with the provided name. + * + * @param ir the IR where to search the definition + * @param name the definition name to look for + * @return the list of definitions with the provided name + */ + def findModuleDefinitions(ir: IR, name: String): Set[IR] = { + val builder = Set.newBuilder[IR] + IR.preorder( + ir, + { + case methodExplicit: Method.Explicit + if methodExplicit.methodName.name == name => + builder.addOne(methodExplicit) + case _ => + } + ) + builder.result() + } + + /** Find definitions with the provided name. + * + * @param scope the IR where to search the definition + * @param name the definition name to look for + * @return the list of definitions with the provided name + */ + def findLocalDefinitions(scope: IR, name: String): Set[IR] = { + val builder = Set.newBuilder[IR] + IR.preorder( + scope, + { + case expressionBinding: Expression.Binding + if expressionBinding.name.name == name => + builder.addOne(expressionBinding) + case _ => + } + ) + builder.result() + } + + /** Get the [[Expression.Block]] containing the provided expression. + * + * @param scope the scope where to look + * @param expression the expression to look for + * @return the block containing the provided expression + */ + def getExpressionBlock( + scope: IR, + expression: IR + ): Option[Expression.Block] = { + val blocksBuilder = Set.newBuilder[Expression.Block] + IR.preorder( + scope, + { + case block: Expression.Block => blocksBuilder.addOne(block) + case _ => + } + ) + val blocks = blocksBuilder.result() + + blocks.find(block => findById(block, expression.getId).isDefined) + } + /** Find usages of a local defined in the body block. * * @param ir the syntax tree @@ -63,7 +127,7 @@ trait IRUtils { node: Name ): Option[Set[Name.Literal]] = for { - usages <- findDynamicUsages(ir, node) + usages <- findDynamicUsages(ir, node.name) } yield { usages.collect { case Application.Prefix(function: Name.Literal, args, _, _, _) @@ -117,16 +181,16 @@ trait IRUtils { /** Find usages of a dynamic dependency in the [[DataflowAnalysis]] metadata. * * @param ir the syntax tree - * @param node the name to look for + * @param name the name to look for * @return the list of usages of the given name in the `ir` */ private def findDynamicUsages( ir: IR, - node: Name + name: String ): Option[Set[IR]] = { for { metadata <- ir.getMetadata(DataflowAnalysis) - key = DataflowAnalysis.DependencyInfo.Type.Dynamic(node.name, None) + key = DataflowAnalysis.DependencyInfo.Type.Dynamic(name, None) dependents <- metadata.dependents.get(key) } yield { dependents diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/RefactoringRenameJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/RefactoringRenameJob.scala index dec4f9e0914e..06cc180b90d1 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/RefactoringRenameJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/RefactoringRenameJob.scala @@ -58,6 +58,13 @@ final class RefactoringRenameJob( ) ) Seq() + case ex: RefactoringRenameJob.DefinitionAlreadyExists => + reply( + Api.SymbolRenameFailed( + Api.SymbolRenameFailed.DefinitionAlreadyExists(ex.name) + ) + ) + Seq() case ex: RefactoringRenameJob.FailedToApplyEdits => reply( Api.SymbolRenameFailed( @@ -95,6 +102,26 @@ final class RefactoringRenameJob( throw new RefactoringRenameJob.OperationNotSupported(expressionId) ) + // check if global definition exists + methodDefinition.foreach { _ => + val moduleDefs = + IRUtils.findModuleDefinitions(module.getIr, newSymbolName) + if (moduleDefs.nonEmpty) { + throw new RefactoringRenameJob.DefinitionAlreadyExists(newSymbolName) + } + } + + // check if local definition exists + local.foreach { symbol => + val scopeOpt = IRUtils.getExpressionBlock(module.getIr, symbol) + scopeOpt.foreach { scope => + val localDefs = IRUtils.findLocalDefinitions(scope, newSymbolName) + if (localDefs.nonEmpty) { + throw new RefactoringRenameJob.DefinitionAlreadyExists(newSymbolName) + } + } + } + def localUsages = local.flatMap(IRUtils.findLocalUsages(module.getIr, _)) def methodDefinitionUsages = methodDefinition.flatMap( IRUtils.findModuleMethodUsages(module.getName, module.getIr, _) @@ -179,6 +206,9 @@ object RefactoringRenameJob { final private class ExpressionNotFound(val expressionId: UUID @ExternalID) extends Exception(s"Expression was not found by id [$expressionId].") + final private class DefinitionAlreadyExists(val name: String) + extends Exception(s"Definition [$name] already exists in scope") + final private class FailedToApplyEdits(val module: String) extends Exception(s"Failed to apply edits to module [$module]") diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRefactoringTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRefactoringTest.scala index c82dde5108ec..9ed928d90fd1 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRefactoringTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRefactoringTest.scala @@ -1067,4 +1067,235 @@ class RuntimeRefactoringTest ) context.consumeOut shouldEqual List() } + + it should "fail to rename module method when module definition exists" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val idFunction1 = metadata.addItem(31, 9) + val code = + """from Standard.Base import all + | + |function1 x = x + 1 + | + |function2 = Nothing + | + |main = + | operator1 = 41 + | operator2 = x -> Main.function1 x + | IO.println (operator2 operator1) + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // open file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("42") + + // rename operator1 + val newName = "function2" + context.send( + Api.Request(requestId, Api.RenameSymbol(moduleName, idFunction1, newName)) + ) + context.receiveNIgnoreStdLib(1) should contain theSameElementsAs Seq( + Api.Response( + requestId, + Api.SymbolRenameFailed( + Api.SymbolRenameFailed.DefinitionAlreadyExists(newName) + ) + ) + ) + context.consumeOut shouldEqual List() + } + + it should "fail to rename operator when local definition exists" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val idOperator1 = metadata.addItem(42, 9) + val code = + """from Standard.Base import all + | + |main = + | operator1 = 41 + | operator2 = operator1 + 1 + | IO.println operator2 + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // open file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("42") + + // rename operator1 + val newName = "operator2" + context.send( + Api.Request(requestId, Api.RenameSymbol(moduleName, idOperator1, newName)) + ) + context.receiveNIgnoreStdLib(1) should contain theSameElementsAs Seq( + Api.Response( + requestId, + Api.SymbolRenameFailed( + Api.SymbolRenameFailed.DefinitionAlreadyExists(newName) + ) + ) + ) + context.consumeOut shouldEqual List() + } + + it should "rename operator if the same definition exists in different method body" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val newName = "foobarbaz" + + val metadata = new Metadata + val idOperator1 = metadata.addItem(42, 9) + val code = + s"""from Standard.Base import all + | + |main = + | operator1 = 41 + | operator2 = operator1 + 1 + | IO.println operator2 + | + |test = + | $newName = 42 + | $newName + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // open file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("42") + + // rename operator1 + val expectedEdits = Vector( + TextEdit( + model.Range(model.Position(3, 4), model.Position(3, 13)), + newName + ), + TextEdit( + model.Range(model.Position(4, 16), model.Position(4, 25)), + newName + ) + ) + val expectedFileEdit = Api.FileEdit( + context.pkg.mainFile, + expectedEdits, + versionCalculator.evalVersion(contents).toHexString, + versionCalculator + .evalVersion(contents.replaceAll("operator1", newName)) + .toHexString + ) + context.send( + Api.Request(requestId, Api.RenameSymbol(moduleName, idOperator1, newName)) + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.SymbolRenamed(newName)), + Api.Response(None, expectedFileEdit), + TestMessages.pending(contextId, idOperator1), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("42") + } } From 173478f29a6597191aa432a7622a8156799f823b Mon Sep 17 00:00:00 2001 From: marthasharkey Date: Tue, 17 Dec 2024 17:01:09 +0000 Subject: [PATCH 06/19] Wip/mk/display message on warning (#11705) --- .../JSONVisualization/JsonErrorWidget.vue | 16 ++++++++ .../JSONVisualization/JsonValueWidget.vue | 7 +++- .../visualizations/WarningsVisualization.vue | 40 ++++++++++++++++--- 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 app/gui/src/project-view/components/visualizations/JSONVisualization/JsonErrorWidget.vue diff --git a/app/gui/src/project-view/components/visualizations/JSONVisualization/JsonErrorWidget.vue b/app/gui/src/project-view/components/visualizations/JSONVisualization/JsonErrorWidget.vue new file mode 100644 index 000000000000..b98c141ffce7 --- /dev/null +++ b/app/gui/src/project-view/components/visualizations/JSONVisualization/JsonErrorWidget.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/app/gui/src/project-view/components/visualizations/JSONVisualization/JsonValueWidget.vue b/app/gui/src/project-view/components/visualizations/JSONVisualization/JsonValueWidget.vue index 894ffaaad5fa..3b23a1141696 100644 --- a/app/gui/src/project-view/components/visualizations/JSONVisualization/JsonValueWidget.vue +++ b/app/gui/src/project-view/components/visualizations/JSONVisualization/JsonValueWidget.vue @@ -1,5 +1,6 @@