diff --git a/AST.fu b/AST.fu index fb2866d6..387c9c14 100644 --- a/AST.fu +++ b/AST.fu @@ -1246,6 +1246,7 @@ public class FuMethod : FuMethodBase } public override bool IsStatic() => this.CallType == FuCallType.Static; public bool IsAbstractOrVirtual() => this.CallType == FuCallType.Abstract || this.CallType == FuCallType.Virtual; + public bool IsAbstractVirtualOrOverride() => this.CallType == FuCallType.Abstract || this.CallType == FuCallType.Virtual || this.CallType == FuCallType.Override; public FuVar!? FirstParameter() { assert this.Parameters.First is FuVar! first; // FIXME: FuVar!? diff --git a/GenC.fu b/GenC.fu index 996545dd..1ab1d4e1 100644 --- a/GenC.fu +++ b/GenC.fu @@ -1813,11 +1813,8 @@ public class GenC : GenCCpp WriteUpcast(declaringClass, klass.Parent); } else { - FuClass definingClass = declaringClass; - switch (method.CallType) { - case FuCallType.Abstract: - case FuCallType.Virtual: - case FuCallType.Override: + if (method.IsAbstractVirtualOrOverride()) { + FuClass definingClass = declaringClass; if (method.CallType == FuCallType.Override) { assert method.GetDeclaringMethod().Parent is FuClass declaringClass1; declaringClass = declaringClass1; @@ -1842,11 +1839,9 @@ public class GenC : GenCCpp WriteChar(')'); Write("->"); WriteCamelCase(method.Name); - break; - default: - WriteName(method); - break; } + else + WriteName(method); WriteChar('('); if (method.CallType != FuCallType.Static) { if (obj != null) diff --git a/Sema.fu b/Sema.fu index d3a1fbed..23c51537 100644 --- a/Sema.fu +++ b/Sema.fu @@ -2228,20 +2228,13 @@ public class FuSema if (method.CallType == FuCallType.Override || method.CallType == FuCallType.Sealed) { if (klass.Parent.TryLookup(method.Name, false) is FuMethod! baseMethod) { // TODO: check private - switch (baseMethod.CallType) { - case FuCallType.Abstract: - case FuCallType.Virtual: - case FuCallType.Override: - if (method.IsMutator() != baseMethod.IsMutator()) { - if (method.IsMutator()) - ReportError(method, "Mutating method cannot override a non-mutating method"); - else - ReportError(method, "Non-mutating method cannot override a mutating method"); - } - break; - default: + if (!baseMethod.IsAbstractVirtualOrOverride()) ReportError(method, "Base method is not abstract or virtual"); - break; + else if (method.IsMutator() != baseMethod.IsMutator()) { + if (method.IsMutator()) + ReportError(method, "Mutating method cannot override a non-mutating method"); + else + ReportError(method, "Non-mutating method cannot override a mutating method"); } if (!method.Type.EqualsType(baseMethod.Type)) ReportError(method.Type, "Base method has a different return type"); diff --git a/editors/vscode/README.md b/editors/vscode/README.md index 6935e0b5..bed7b608 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -2,9 +2,10 @@ A Visual Studio Code extension for the [Fusion programming language](https://fus * Syntax highlighting * Error highlighting -* Outline -* Go to definition -* Go to references, Find all references +* Breadcrumbs, Outline +* Go to / Peek definition +* Go to / Peek / Find all implementations +* Go to / Peek / Find all references * Snippets Fusion is a language designed to be translated automatically to diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index 94f80910..9e4de8e5 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -19,7 +19,7 @@ // along with Fusion Transpiler. If not, see http://www.gnu.org/licenses/ import * as vscode from "vscode"; -import { FuParser, FuProgram, FuSystem, FuSema, FuSemaHost, FuSymbolReferenceVisitor, FuStatement, FuSymbol, FuContainerType, FuEnum, FuMember, FuField, FuMethod } from "./fucheck.js"; +import { FuParser, FuProgram, FuSystem, FuSema, FuSemaHost, FuSymbolReferenceVisitor, FuStatement, FuSymbol, FuContainerType, FuEnum, FuClass, FuMember, FuField, FuMethod } from "./fucheck.js"; class VsCodeHost extends FuSemaHost { @@ -73,29 +73,6 @@ class VsCodeHost extends FuSemaHost sema.setHost(this); sema.process(); } - - async findSymbol(document: vscode.TextDocument, position: vscode.Position): Promise - { - const parser = this.createParser(); - const filename = document.uri.toString(); - parser.findName(filename, position.line, position.character); - const files = await vscode.workspace.findFiles("*.fu"); - if (files.some(uri => uri.toString() == filename)) - await this.parseFolder(files, parser); - else - this.parseDocument(document, parser); - this.doSema(); - return parser.getFoundDefinition(); - } - - toLocation(statement: FuStatement): vscode.Location | null - { - if (statement.loc <= 0) - return null; - const line = this.program.getLine(statement.loc); - const file = this.program.getSourceFile(line); - return new vscode.Location(vscode.Uri.parse(file.filename), new vscode.Position(line - file.line, statement.loc - this.program.lineLocs[line])); - } } class VsCodeDiagnostics extends VsCodeHost @@ -159,39 +136,87 @@ class VsCodeDiagnostics extends VsCodeHost } } -class VsCodeDefinitionProvider extends VsCodeHost -{ - async findDefinition(document: vscode.TextDocument, position: vscode.Position): Promise - { - const symbol = await this.findSymbol(document, position); - return symbol == null ? null : this.toLocation(symbol); - } -} - class VsCodeReferenceCollector extends FuSymbolReferenceVisitor { - host: VsCodeHost; + provider: VsCodeGotoProvider; result: vscode.Location[] = []; - constructor(host: VsCodeHost) + constructor(provider: VsCodeGotoProvider) { super(); - this.host = host; + this.provider = provider; } visitFound(reference: FuStatement): void { - const location = this.host.toLocation(reference); - if (location != null) - this.result.push(location); + this.provider.pushLocation(this.result, reference); } } -class VsCodeReferenceProvider extends VsCodeHost +class VsCodeGotoProvider extends VsCodeHost { + async #findSymbol(document: vscode.TextDocument, position: vscode.Position): Promise + { + const parser = this.createParser(); + const filename = document.uri.toString(); + parser.findName(filename, position.line, position.character); + const files = await vscode.workspace.findFiles("*.fu"); + if (files.some(uri => uri.toString() == filename)) + await this.parseFolder(files, parser); + else + this.parseDocument(document, parser); + this.doSema(); + return parser.getFoundDefinition(); + } + + #toLocation(statement: FuStatement): vscode.Location | null + { + if (statement.loc <= 0) + return null; + const line = this.program.getLine(statement.loc); + const file = this.program.getSourceFile(line); + return new vscode.Location(vscode.Uri.parse(file.filename), new vscode.Position(line - file.line, statement.loc - this.program.lineLocs[line])); + } + + pushLocation(result: vscode.Location[], statement: FuStatement): void + { + const location = this.#toLocation(statement); + if (location != null) + result.push(location); + } + + async findDefinition(document: vscode.TextDocument, position: vscode.Position): Promise + { + const symbol = await this.#findSymbol(document, position); + return symbol == null ? null : this.#toLocation(symbol); + } + + async findImplementations(document: vscode.TextDocument, position: vscode.Position): Promise + { + const symbol = await this.#findSymbol(document, position); + const result: vscode.Location[] = []; + if (symbol != null) { + if (symbol instanceof FuClass) { + for (const subclass of this.program.classes) { + if (symbol.isSameOrBaseOf(subclass)) + this.pushLocation(result, subclass); + } + } + else if (symbol instanceof FuMethod && symbol.isAbstractVirtualOrOverride()) { + for (const subclass of this.program.classes) { + if (symbol.parent.isSameOrBaseOf(subclass) && subclass.contains(symbol)) + this.pushLocation(result, subclass.tryLookup(symbol.name, false)); + } + } + else + this.pushLocation(result, symbol); + } + return result; + } + async findReferences(document: vscode.TextDocument, position: vscode.Position): Promise { - const symbol = await this.findSymbol(document, position); + const symbol = await this.#findSymbol(document, position); if (symbol == null) return []; const collector = new VsCodeReferenceCollector(this); @@ -250,12 +275,17 @@ export function activate(context: vscode.ExtensionContext): void context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(document => diagnostics.delete(document))); vscode.languages.registerDefinitionProvider("fusion", { provideDefinition(document, position, token) { - return new VsCodeDefinitionProvider().findDefinition(document, position); + return new VsCodeGotoProvider().findDefinition(document, position); + } + }); + vscode.languages.registerImplementationProvider("fusion", { + provideImplementation(document, position, token) { + return new VsCodeGotoProvider().findImplementations(document, position); } }); vscode.languages.registerReferenceProvider("fusion", { provideReferences(document, position, context, token) { - return new VsCodeReferenceProvider().findReferences(document, position); + return new VsCodeGotoProvider().findReferences(document, position); } }); vscode.languages.registerDocumentSymbolProvider("fusion", { diff --git a/libfut.cpp b/libfut.cpp index c0684569..a4abf0cd 100644 --- a/libfut.cpp +++ b/libfut.cpp @@ -2270,6 +2270,11 @@ bool FuMethod::isAbstractOrVirtual() const return this->callType == FuCallType::abstract || this->callType == FuCallType::virtual_; } +bool FuMethod::isAbstractVirtualOrOverride() const +{ + return this->callType == FuCallType::abstract || this->callType == FuCallType::virtual_ || this->callType == FuCallType::override_; +} + FuVar * FuMethod::firstParameter() const { FuVar * first = static_cast(this->parameters.first); @@ -6650,20 +6655,13 @@ void FuSema::resolveCode(FuClass * klass) if (method->body != nullptr) { if (method->callType == FuCallType::override_ || method->callType == FuCallType::sealed) { if (FuMethod *baseMethod = dynamic_cast(klass->parent->tryLookup(method->name, false).get())) { - switch (baseMethod->callType) { - case FuCallType::abstract: - case FuCallType::virtual_: - case FuCallType::override_: - if (method->isMutator() != baseMethod->isMutator()) { - if (method->isMutator()) - reportError(method, "Mutating method cannot override a non-mutating method"); - else - reportError(method, "Non-mutating method cannot override a mutating method"); - } - break; - default: + if (!baseMethod->isAbstractVirtualOrOverride()) reportError(method, "Base method is not abstract or virtual"); - break; + else if (method->isMutator() != baseMethod->isMutator()) { + if (method->isMutator()) + reportError(method, "Mutating method cannot override a non-mutating method"); + else + reportError(method, "Non-mutating method cannot override a mutating method"); } if (!method->type->equalsType(baseMethod->type.get())) reportError(method->type.get(), "Base method has a different return type"); @@ -11067,42 +11065,35 @@ void GenC::writeCCall(const FuExpr * obj, const FuMethod * method, const std::ve writeUpcast(declaringClass, klass->parent); } else { - const FuClass * definingClass = declaringClass; - switch (method->callType) { - case FuCallType::abstract: - case FuCallType::virtual_: - case FuCallType::override_: + if (method->isAbstractVirtualOrOverride()) { + const FuClass * definingClass = declaringClass; if (method->callType == FuCallType::override_) { const FuClass * declaringClass1 = static_cast(method->getDeclaringMethod()->parent); declaringClass = declaringClass1; } if (obj != nullptr) klass = obj->type->asClassType()->class_; - { - const FuClass * ptrClass = getVtblPtrClass(klass); - const FuClass * structClass = getVtblStructClass(definingClass); - if (structClass != ptrClass) { - write("((const "); - writeName(structClass); - write("Vtbl *) "); - } - if (obj != nullptr) { - obj->accept(this, FuPriority::primary); - writeMemberAccess(obj->type.get(), ptrClass); - } - else - writeSelfForField(ptrClass); - write("vtbl"); - if (structClass != ptrClass) - writeChar(')'); - write("->"); - writeCamelCase(method->name); - break; + const FuClass * ptrClass = getVtblPtrClass(klass); + const FuClass * structClass = getVtblStructClass(definingClass); + if (structClass != ptrClass) { + write("((const "); + writeName(structClass); + write("Vtbl *) "); } - default: - writeName(method); - break; + if (obj != nullptr) { + obj->accept(this, FuPriority::primary); + writeMemberAccess(obj->type.get(), ptrClass); + } + else + writeSelfForField(ptrClass); + write("vtbl"); + if (structClass != ptrClass) + writeChar(')'); + write("->"); + writeCamelCase(method->name); } + else + writeName(method); writeChar('('); if (method->callType != FuCallType::static_) { if (obj != nullptr) diff --git a/libfut.cs b/libfut.cs index f671d7ed..4dca8a57 100644 --- a/libfut.cs +++ b/libfut.cs @@ -2804,6 +2804,8 @@ public static FuMethod New(FuClass klass, FuVisibility visibility, FuCallType ca public bool IsAbstractOrVirtual() => this.CallType == FuCallType.Abstract || this.CallType == FuCallType.Virtual; + public bool IsAbstractVirtualOrOverride() => this.CallType == FuCallType.Abstract || this.CallType == FuCallType.Virtual || this.CallType == FuCallType.Override; + public FuVar FirstParameter() { FuVar first = (FuVar) this.Parameters.First; @@ -6757,20 +6759,13 @@ void ResolveCode(FuClass klass) if (method.Body != null) { if (method.CallType == FuCallType.Override || method.CallType == FuCallType.Sealed) { if (klass.Parent.TryLookup(method.Name, false) is FuMethod baseMethod) { - switch (baseMethod.CallType) { - case FuCallType.Abstract: - case FuCallType.Virtual: - case FuCallType.Override: - if (method.IsMutator() != baseMethod.IsMutator()) { - if (method.IsMutator()) - ReportError(method, "Mutating method cannot override a non-mutating method"); - else - ReportError(method, "Non-mutating method cannot override a mutating method"); - } - break; - default: + if (!baseMethod.IsAbstractVirtualOrOverride()) ReportError(method, "Base method is not abstract or virtual"); - break; + else if (method.IsMutator() != baseMethod.IsMutator()) { + if (method.IsMutator()) + ReportError(method, "Mutating method cannot override a non-mutating method"); + else + ReportError(method, "Non-mutating method cannot override a mutating method"); } if (!method.Type.EqualsType(baseMethod.Type)) ReportError(method.Type, "Base method has a different return type"); @@ -11334,11 +11329,8 @@ protected void WriteCCall(FuExpr obj, FuMethod method, List args) WriteUpcast(declaringClass, klass.Parent); } else { - FuClass definingClass = declaringClass; - switch (method.CallType) { - case FuCallType.Abstract: - case FuCallType.Virtual: - case FuCallType.Override: + if (method.IsAbstractVirtualOrOverride()) { + FuClass definingClass = declaringClass; if (method.CallType == FuCallType.Override) { FuClass declaringClass1 = (FuClass) method.GetDeclaringMethod().Parent; declaringClass = declaringClass1; @@ -11363,11 +11355,9 @@ protected void WriteCCall(FuExpr obj, FuMethod method, List args) WriteChar(')'); Write("->"); WriteCamelCase(method.Name); - break; - default: - WriteName(method); - break; } + else + WriteName(method); WriteChar('('); if (method.CallType != FuCallType.Static) { if (obj != null) diff --git a/libfut.hpp b/libfut.hpp index a35396fb..87a3a00d 100644 --- a/libfut.hpp +++ b/libfut.hpp @@ -1331,6 +1331,7 @@ class FuMethod : public FuMethodBase static std::shared_ptr new_(const FuClass * klass, FuVisibility visibility, FuCallType callType, std::shared_ptr type, FuId id, std::string_view name, bool isMutator, std::shared_ptr param0 = nullptr, std::shared_ptr param1 = nullptr, std::shared_ptr param2 = nullptr, std::shared_ptr param3 = nullptr); bool isStatic() const override; bool isAbstractOrVirtual() const; + bool isAbstractVirtualOrOverride() const; FuVar * firstParameter() const; int getParametersCount() const; const FuMethod * getDeclaringMethod() const; diff --git a/libfut.js b/libfut.js index 84138ec0..938350c6 100644 --- a/libfut.js +++ b/libfut.js @@ -2935,6 +2935,11 @@ export class FuMethod extends FuMethodBase return this.callType == FuCallType.ABSTRACT || this.callType == FuCallType.VIRTUAL; } + isAbstractVirtualOrOverride() + { + return this.callType == FuCallType.ABSTRACT || this.callType == FuCallType.VIRTUAL || this.callType == FuCallType.OVERRIDE; + } + firstParameter() { let first = this.parameters.first; @@ -7131,20 +7136,13 @@ export class FuSema if (method.callType == FuCallType.OVERRIDE || method.callType == FuCallType.SEALED) { let baseMethod; if ((baseMethod = klass.parent.tryLookup(method.name, false)) instanceof FuMethod) { - switch (baseMethod.callType) { - case FuCallType.ABSTRACT: - case FuCallType.VIRTUAL: - case FuCallType.OVERRIDE: - if (method.isMutator() != baseMethod.isMutator()) { - if (method.isMutator()) - this.#reportError(method, "Mutating method cannot override a non-mutating method"); - else - this.#reportError(method, "Non-mutating method cannot override a mutating method"); - } - break; - default: + if (!baseMethod.isAbstractVirtualOrOverride()) this.#reportError(method, "Base method is not abstract or virtual"); - break; + else if (method.isMutator() != baseMethod.isMutator()) { + if (method.isMutator()) + this.#reportError(method, "Mutating method cannot override a non-mutating method"); + else + this.#reportError(method, "Non-mutating method cannot override a mutating method"); } if (!method.type.equalsType(baseMethod.type)) this.#reportError(method.type, "Base method has a different return type"); @@ -11763,11 +11761,8 @@ export class GenC extends GenCCpp this.#writeUpcast(declaringClass, klass.parent); } else { - let definingClass = declaringClass; - switch (method.callType) { - case FuCallType.ABSTRACT: - case FuCallType.VIRTUAL: - case FuCallType.OVERRIDE: + if (method.isAbstractVirtualOrOverride()) { + let definingClass = declaringClass; if (method.callType == FuCallType.OVERRIDE) { let declaringClass1 = method.getDeclaringMethod().parent; declaringClass = declaringClass1; @@ -11792,11 +11787,9 @@ export class GenC extends GenCCpp this.writeChar(41); this.write("->"); this.writeCamelCase(method.name); - break; - default: - this.writeName(method); - break; } + else + this.writeName(method); this.writeChar(40); if (method.callType != FuCallType.STATIC) { if (obj != null)