-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Imports conflict resolution #9093
Conversation
} | ||
|
||
/* Substitute `name` inside `expression` with `to`. */ | ||
export function substituteQualifiedName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a major flaw in the current implementation, and I’m not sure how to overcome it.
The failing test case would be:
// …
const expr = Ast.parse('Name', edit)
substituteQualifiedName(edit, expr, 'Name', 'OtherName' as QualifiedName)
This will not change expr
, because updateValue
requires some parent, but expr
has none.
I overcome it by doing conflict resolution later, when we already have assignment ast: variable = expr
, and in this case expr
always has the parent, so updateValue
works.
How it should be done?
cc @kazcw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would separate substituteQualifiedName
into two functions:
A QN operation:
getQualifiedNameSubstitution(expression: Ast, name, to): Ast.Owned | undefined
A higher-order recursive-mutation helper:
substituteRecursive(expression: MutableAst, f: (ast: MutableAst) => Owned | undefined)
If your use-case needs it, substituteRecursive
could return the new Owned
value if it replaces a value that had no parent--but I would expect it is possible to structure things so that that is not needed (i.e. depending on situation use getQualifiedNameSubstitution
directly or use it with substituteRecursive
), and that would be IMO clearer.
The engine error that you see on the recording is caused by the current importing rule, which implies that the item should be exported from |
const importedIdent = | ||
anImport.kind == 'Qualified' ? qnLastSegment(anImport.module) : anImport.import | ||
const noLongerNeeded = !code.value.includes(importedIdent) | ||
if (!noLongerNeeded && !alreadyAdded) { | ||
filtered.push(anImport) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that's an older code, but what about imports for extension methods? These imports may be from any module, whose name is not in the input at all.
const noLongerNeeded = !code.value.includes(importedIdent) | ||
if (!noLongerNeeded && !alreadyAdded) { | ||
finalImports.push(anImport) | ||
function importsToAdd(): ImportsForEntry[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite like this API change. Every array element is ImportsForEntry
but technically not all will contain all "Imports for entry", as the latter may have imports filtered out.
Do we use anywhere this information (i.e. which import was to what entry) outside? Maybe we could just return flat RequiredImport
instead?
@@ -44,7 +44,7 @@ const value = computed({ | |||
if (requiresImport) graph.addMissingImports(edit, theImport) | |||
props.onUpdate({ edit }) | |||
} else { | |||
graph.addMissingImports(edit, theImport) | |||
graph.addMissingImportsDisregardConflicts(edit, theImport) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we disregard conflicts here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, I’m not sure what was the initial reasoning here.
app/gui2/src/stores/graph/imports.ts
Outdated
if (!entry) return | ||
const conflictingIds = suggestionDb.conflictingNames.lookup(entry.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not look like valid code. If we are about to add Table
import for making Table.new
node, we should look up conflicts with Table
, not new
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’re correct, it was fixed
// Keep in mind, that we need to provide the id of `entry`, not `requiredEntry` here! | ||
imports.value.push({ id, imports: requiredImports(suggestionDb, requiredEntry) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cannot we gather here actually imported entries only? For example, when we add Table.new
node: We should check conflicts for Table
imports and only Table
needs to be replaced with its fully qualified name, not entire chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, it was indeed an overcomplication of things.
app/gui2/src/stores/graph/imports.ts
Outdated
const usedAs = | ||
entry.memberOf != null | ||
? (`${qnLastSegment(entry.memberOf)}.${entry.name}` as QualifiedName) | ||
: entry.name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if user wrote partially qualified name? For example, Data.Vector.new
, and importing Data
makes conflict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
@@ -412,7 +412,7 @@ function hideComponentBrowser() { | |||
componentBrowserVisible.value = false | |||
} | |||
|
|||
function onComponentBrowserCommit(content: string, requiredImports: RequiredImport[]) { | |||
function onComponentBrowserCommit(content: string, requiredImports: ImportsForEntry[]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait a sec, we do not add imports altogether when editing nodes! An older code too, but this time I guess we may just fix it in a separate issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s an oversight, yes. I would add it in the follow-up: #9241
} | ||
|
||
/* Substitute `name` inside `expression` with `to`. */ | ||
export function substituteQualifiedName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This substitution won't work in cases like Foo.bar (Foo -> Foo.baz)
- here we should replace only one Foo!
I thought a bit, but I don't know how to tackle it. Component Browser Input already analyses its content to know what identifiers are from outside, so it could use the information here also. On the other hand, I'm aware we also want to track import conflicts in other cases than inserting/editing node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, we would need to refactor CB input logic to work with AST directly for this, and even then it can still be complicated.
const suggestionId = component?.suggestionId ?? selectedSuggestionId.value | ||
if (suggestion == null || suggestionId == null) return null | ||
input.applySuggestion(suggestionId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is here still a need for obtaining suggestion
? We could just pass suggestionId and applySuggestion
should handle that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (requiredEntry) { | ||
// Keep in mind, that we need to provide the id of `entry`, not `requiredEntry` here! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment doesn't seem to be relevant anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
app/gui2/src/stores/graph/imports.ts
Outdated
const [entryId] = suggestionDb.nameToId.lookup(entryFQN) | ||
if (entryId == null) return | ||
const entry = suggestionDb.get(entryId)! | ||
const conflictingIds = suggestionDb.conflictingNames.lookup(entry.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, we could just read the last segment of entryFQN
, as name, and don't fetch the entry (only entryId
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, fixed
Pull Request Description
Closes #5353
When name conflict is detected, we use fully qualified name instead of the usual one.
imports.conflict.resolution.mp4
Important Notes
Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
Scala,
Java,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.
./run ide build
.