From fbdb4d1248ec16342419efdba8842a603723b955 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 24 Nov 2025 14:57:01 +0000 Subject: [PATCH 1/9] Begin adding more parser tests. --- compiler/src/graphalg/parse/Parser.cpp | 32 +++++++++++++++++-- compiler/test/parse-err/func-name-dup.gr | 11 +++++++ compiler/test/parse-err/func-param-dup.gr | 9 ++++++ .../test/parse-err/matrix-fill-col-vector.gr | 8 +++++ compiler/test/parse-err/vector-fill-matrix.gr | 8 +++++ .../test/parse-err/vector-fill-row-vector.gr | 8 +++++ 6 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 compiler/test/parse-err/func-name-dup.gr create mode 100644 compiler/test/parse-err/func-param-dup.gr create mode 100644 compiler/test/parse-err/matrix-fill-col-vector.gr create mode 100644 compiler/test/parse-err/vector-fill-matrix.gr create mode 100644 compiler/test/parse-err/vector-fill-row-vector.gr diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index f85d778..40da4cf 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -452,7 +453,12 @@ mlir::ParseResult Parser::parseFunction() { return mlir::failure(); } - // TODO: Check duplicate definition + if (auto *previousDef = _module.lookupSymbol(name)) { + auto diag = mlir::emitError(loc) + << "duplicate definition of function '" << name << "'"; + diag.attachNote(previousDef->getLoc()) << "original definition here"; + return diag; + } // Create the new op. auto funcType = _builder.getFunctionType(paramTypes, {returnType}); @@ -496,11 +502,15 @@ Parser::parseParams(llvm::SmallVectorImpl &names, // First parameter auto &name = names.emplace_back(); auto &type = types.emplace_back(); - locs.emplace_back(cur().loc); + auto loc = cur().loc; + locs.emplace_back(loc); if (parseIdent(name) || eatOrError(Token::COLON) || parseType(type)) { return mlir::failure(); } + llvm::SmallDenseMap previousParams = { + {name, loc}}; + while (cur().type != Token::RPAREN) { // More parameters if (eatOrError(Token::COMMA)) { @@ -509,10 +519,21 @@ Parser::parseParams(llvm::SmallVectorImpl &names, auto &name = names.emplace_back(); auto &type = types.emplace_back(); - locs.emplace_back(cur().loc); + auto loc = cur().loc; + locs.emplace_back(loc); if (parseIdent(name) || eatOrError(Token::COLON) || parseType(type)) { return mlir::failure(); } + + // Check for duplicate parameter names. + if (previousParams.contains(name)) { + auto diag = mlir::emitError(loc) + << "duplicate parameter name '" << name << "'"; + diag.attachNote(previousParams.at(name)) << "previous definition here"; + return diag; + } + + previousParams.insert({name, loc}); } return eatOrError(Token::RPAREN); @@ -923,6 +944,11 @@ mlir::Value Parser::applyFill(mlir::Location baseLoc, mlir::Value base, << "vector fill [:] used with non-vector base"; diag.attachNote(baseLoc) << "base has type " << typeToString(baseType); return nullptr; + } else if (fill == ParsedFill::MATRIX && baseType.isColumnVector()) { + auto diag = mlir::emitError(fillLoc) + << "matrix fill [:, :] used with column vector base"; + diag.attachNote(baseLoc) << "base has type " << typeToString(baseType); + return nullptr; } return _builder.create(fillLoc, baseType, expr); diff --git a/compiler/test/parse-err/func-name-dup.gr b/compiler/test/parse-err/func-name-dup.gr new file mode 100644 index 0000000..ad6e1e8 --- /dev/null +++ b/compiler/test/parse-err/func-name-dup.gr @@ -0,0 +1,11 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{original definition here}} +func Dup(a: int) -> int { + return a; +} + +// expected-error@below{{duplicate definition of function 'Dup'}} +func Dup(a: int) -> int { + return a; +} diff --git a/compiler/test/parse-err/func-param-dup.gr b/compiler/test/parse-err/func-param-dup.gr new file mode 100644 index 0000000..ca6bf1c --- /dev/null +++ b/compiler/test/parse-err/func-param-dup.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func Dup( + // expected-note@below{{previous definition here}} + a: int, + // expected-error@below{{duplicate parameter name 'a'}} + a: int) -> int { + return a; +} diff --git a/compiler/test/parse-err/matrix-fill-col-vector.gr b/compiler/test/parse-err/matrix-fill-col-vector.gr new file mode 100644 index 0000000..f5d6df8 --- /dev/null +++ b/compiler/test/parse-err/matrix-fill-col-vector.gr @@ -0,0 +1,8 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func MatrixFillColVector(v: Vector, x: int) -> Vector { + // expected-error@below{{matrix fill [:, :] used with column vector base}} + // expected-note@below{{base has type Vector}} + v[:, :] = x; + return v; +} diff --git a/compiler/test/parse-err/vector-fill-matrix.gr b/compiler/test/parse-err/vector-fill-matrix.gr new file mode 100644 index 0000000..8f75de0 --- /dev/null +++ b/compiler/test/parse-err/vector-fill-matrix.gr @@ -0,0 +1,8 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func VectorFillMatrix(m: Matrix, x: int) -> Matrix { + // expected-error@below{{vector fill [:] used with non-vector base}} + // expected-note@below{{base has type Matrix}} + m[:] = x; + return m; +} diff --git a/compiler/test/parse-err/vector-fill-row-vector.gr b/compiler/test/parse-err/vector-fill-row-vector.gr new file mode 100644 index 0000000..d2f365b --- /dev/null +++ b/compiler/test/parse-err/vector-fill-row-vector.gr @@ -0,0 +1,8 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func VectorFillRowVector(m: Matrix<1, s, int>, x: int) -> Matrix<1, s, int> { + // expected-error@below{{vector fill [:] used with non-vector base}} + // expected-note@below{{base has type Matrix<1, s, int>}} + m[:] = x; + return m; +} From f1a6c2562a0369523606c7131ec04ebf0fff7e26 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 24 Nov 2025 15:13:39 +0000 Subject: [PATCH 2/9] More assignment tests. --- compiler/src/graphalg/parse/Parser.cpp | 6 ++++-- compiler/test/parse-err/accum-type-mismatch.gr | 8 ++++++++ compiler/test/parse-err/accum-undefined.gr | 7 +++++++ compiler/test/parse-err/loop-scope.gr | 12 ++++++++++++ compiler/test/parse-err/mask-dimension-mismatch.gr | 12 ++++++++++++ compiler/test/parse-err/reassign-type-mismatch.gr | 9 +++++++++ compiler/test/parse-err/vector-fill-non-scalar.gr | 7 +++++++ 7 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 compiler/test/parse-err/accum-type-mismatch.gr create mode 100644 compiler/test/parse-err/accum-undefined.gr create mode 100644 compiler/test/parse-err/loop-scope.gr create mode 100644 compiler/test/parse-err/mask-dimension-mismatch.gr create mode 100644 compiler/test/parse-err/reassign-type-mismatch.gr create mode 100644 compiler/test/parse-err/vector-fill-non-scalar.gr diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index 40da4cf..0cbdb4a 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -255,7 +255,9 @@ void TypeFormatter::formatColumnVector(MatrixType t) { } void TypeFormatter::formatMatrix(MatrixType t) { - if (t.isColumnVector()) { + if (t.isScalar()) { + return formatScalar(t.getSemiring()); + } else if (t.isColumnVector()) { return formatColumnVector(t); } @@ -968,7 +970,7 @@ mlir::ParseResult Parser::parseStmtAccum(mlir::Location baseLoc, return mlir::emitError(baseLoc) << "type of base does not match the expression to accumulate: (" << typeToString(baseValue.getType()) << " vs. " - << typeToString(expr.getType()); + << typeToString(expr.getType()) << ")"; } // Rewrite a += b; to a = a (.+) b; diff --git a/compiler/test/parse-err/accum-type-mismatch.gr b/compiler/test/parse-err/accum-type-mismatch.gr new file mode 100644 index 0000000..b4a5e66 --- /dev/null +++ b/compiler/test/parse-err/accum-type-mismatch.gr @@ -0,0 +1,8 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func AccumTypeMismatch() -> int { + a = int(42); + // expected-error@below{{type of base does not match the expression to accumulate: (int vs. real}} + a += real(3.14); + return int(0); +} diff --git a/compiler/test/parse-err/accum-undefined.gr b/compiler/test/parse-err/accum-undefined.gr new file mode 100644 index 0000000..1e603f8 --- /dev/null +++ b/compiler/test/parse-err/accum-undefined.gr @@ -0,0 +1,7 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func AccumUndefined() -> int { + // expected-error@below{{undefined variable}} + a += int(42); + return int(0); +} diff --git a/compiler/test/parse-err/loop-scope.gr b/compiler/test/parse-err/loop-scope.gr new file mode 100644 index 0000000..4635679 --- /dev/null +++ b/compiler/test/parse-err/loop-scope.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LoopScope() -> int { + a = int(0); + for i in int(1):int(10) { + b = int(42); + a = a + b; + } + // Variable b should not be accessible outside the loop + // expected-error@below{{unrecognized variable}} + return b; +} diff --git a/compiler/test/parse-err/mask-dimension-mismatch.gr b/compiler/test/parse-err/mask-dimension-mismatch.gr new file mode 100644 index 0000000..6a91b46 --- /dev/null +++ b/compiler/test/parse-err/mask-dimension-mismatch.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func MaskDimensionMismatch( + a: Matrix, + m: Matrix, + e: Matrix) -> Matrix { + // expected-error@below{{base dimensions do not match the dimensions of the mask}} + // expected-note@below{{base dimension: (s x s)}} + // expected-note@below{{mask dimensions: (t x t)}} + a = e; + return a; +} diff --git a/compiler/test/parse-err/reassign-type-mismatch.gr b/compiler/test/parse-err/reassign-type-mismatch.gr new file mode 100644 index 0000000..65dfc87 --- /dev/null +++ b/compiler/test/parse-err/reassign-type-mismatch.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func ReassignTypeMismatch() -> int { + // expected-note@below{{previous assigment was here}} + a = int(42); + // expected-error@below{{cannot assign value of type real to previously defined variable of type int}} + a = real(3.14); + return int(0); +} diff --git a/compiler/test/parse-err/vector-fill-non-scalar.gr b/compiler/test/parse-err/vector-fill-non-scalar.gr new file mode 100644 index 0000000..dad881a --- /dev/null +++ b/compiler/test/parse-err/vector-fill-non-scalar.gr @@ -0,0 +1,7 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func VectorFillNonScalar(v: Vector, e: Vector) -> Vector { + // expected-error@below{{fill expression is not a scalar}} + v[:] = e; + return v; +} From 4b7a1a5a1b075bce6102611eb91f8d1019d52f05 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 24 Nov 2025 15:35:57 +0000 Subject: [PATCH 3/9] Loop and return tests. --- compiler/src/graphalg/parse/Parser.cpp | 66 +++- .../test/parse-err/loop-range-end-non-int.gr | 10 + .../parse-err/loop-range-not-dimension.gr | 12 + .../parse-err/loop-range-start-non-int.gr | 10 + .../test/parse-err/loop-until-non-bool.gr | 10 + compiler/test/parse-err/return-in-loop.gr | 9 + compiler/test/parse-err/return-missing.gr | 6 + compiler/test/parse-err/return-multiple.gr | 8 + compiler/test/parse-err/return-not-last.gr | 8 + compiler/test/parse-err/return-wrong-type.gr | 6 + docs/parse-tests.md | 325 ++++++++++++++++++ 11 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 compiler/test/parse-err/loop-range-end-non-int.gr create mode 100644 compiler/test/parse-err/loop-range-not-dimension.gr create mode 100644 compiler/test/parse-err/loop-range-start-non-int.gr create mode 100644 compiler/test/parse-err/loop-until-non-bool.gr create mode 100644 compiler/test/parse-err/return-in-loop.gr create mode 100644 compiler/test/parse-err/return-missing.gr create mode 100644 compiler/test/parse-err/return-multiple.gr create mode 100644 compiler/test/parse-err/return-not-last.gr create mode 100644 compiler/test/parse-err/return-wrong-type.gr create mode 100644 docs/parse-tests.md diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index 0cbdb4a..5246ba1 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -89,6 +89,11 @@ class Parser { DimMapper _dimMapper; + // Track parsing context + int _loopDepth = 0; + bool _hasReturn = false; + mlir::Type _expectedReturnType; + Token cur() { return _tokens[_offset]; } void eat() { @@ -479,11 +484,18 @@ mlir::ParseResult Parser::parseFunction() { } } + // Set expected return type for this function + _expectedReturnType = returnType; + _hasReturn = false; + if (mlir::failed(parseBlock())) { return mlir::failure(); } - // TODO: Check for return statement. + // Check for return statement + if (!_hasReturn) { + return mlir::emitError(loc) << "function must have a return statement"; + } return mlir::success(); } @@ -550,6 +562,12 @@ mlir::ParseResult Parser::parseBlock() { if (parseStmt()) { return mlir::failure(); } + + // Check if there are statements after a return + if (_hasReturn && cur().type != Token::RBRACE) { + return mlir::emitError(cur().loc) + << "statement after return is not allowed"; + } } if (eatOrError(Token::RBRACE)) { @@ -623,6 +641,9 @@ mlir::ParseResult Parser::parseStmtFor() { return mlir::failure(); } + // Increment loop depth + _loopDepth++; + // Find the variables modified inside the loop. llvm::SmallVector varNames; findModifiedBindingsInBlock(_tokens, _offset, varNames); @@ -751,17 +772,41 @@ mlir::ParseResult Parser::parseStmtFor() { } } + // Decrement loop depth + _loopDepth--; + return mlir::success(); } mlir::ParseResult Parser::parseStmtReturn() { auto loc = cur().loc; + + // Check if return is inside a loop + if (_loopDepth > 0) { + return mlir::emitError(loc) + << "return statement inside a loop is not allowed"; + } + + // Check if we already have a return statement + if (_hasReturn) { + return mlir::emitError(loc) << "multiple return statements are not allowed"; + } + mlir::Value returnValue; if (eatOrError(Token::RETURN) || parseExpr(returnValue) || eatOrError(Token::SEMI)) { return mlir::failure(); } + // Check return type matches + if (returnValue.getType() != _expectedReturnType) { + return mlir::emitError(loc) + << "return type mismatch: expected " + << typeToString(_expectedReturnType) << ", but got " + << typeToString(returnValue.getType()); + } + + _hasReturn = true; _builder.create(loc, returnValue); return mlir::success(); } @@ -988,11 +1033,30 @@ mlir::ParseResult Parser::parseRange(ParsedRange &r) { if (cur().type == Token::COLON) { // Const range + auto beginLoc = exprLoc; r.begin = expr; + + auto endLoc = cur().loc; if (eatOrError(Token::COLON) || parseExpr(r.end)) { return mlir::failure(); } + // Check that begin is an integer scalar + auto intScalarType = + MatrixType::scalarOf(SemiringTypes::forInt(_builder.getContext())); + if (r.begin.getType() != intScalarType) { + return mlir::emitError(beginLoc) + << "loop range start must be an integer, but got " + << typeToString(r.begin.getType()); + } + + // Check that end is an integer scalar + if (r.end.getType() != intScalarType) { + return mlir::emitError(endLoc) + << "loop range end must be an integer, but got " + << typeToString(r.end.getType()); + } + return mlir::success(); } else { r.dim = inferDim(expr, exprLoc); diff --git a/compiler/test/parse-err/loop-range-end-non-int.gr b/compiler/test/parse-err/loop-range-end-non-int.gr new file mode 100644 index 0000000..60b0aa5 --- /dev/null +++ b/compiler/test/parse-err/loop-range-end-non-int.gr @@ -0,0 +1,10 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LoopRangeEndNonInt() -> int { + a = int(0); + // expected-error@below{{loop range end must be an integer, but got real}} + for i in int(1):real(10.0) { + a = a + int(1); + } + return a; +} diff --git a/compiler/test/parse-err/loop-range-not-dimension.gr b/compiler/test/parse-err/loop-range-not-dimension.gr new file mode 100644 index 0000000..1a3d52d --- /dev/null +++ b/compiler/test/parse-err/loop-range-not-dimension.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LoopRangeNotDimension(m: Matrix) -> int { + a = int(0); + // m.nvals is an integer, not a dimension + // expected-error@below{{not a dimension type}} + // expected-note@below{{defined here}} + for i in m.nvals { + a = a + int(1); + } + return a; +} diff --git a/compiler/test/parse-err/loop-range-start-non-int.gr b/compiler/test/parse-err/loop-range-start-non-int.gr new file mode 100644 index 0000000..be83ae3 --- /dev/null +++ b/compiler/test/parse-err/loop-range-start-non-int.gr @@ -0,0 +1,10 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LoopRangeStartNonInt() -> int { + a = int(0); + // expected-error@below{{loop range start must be an integer, but got real}} + for i in real(1.0):int(10) { + a = a + int(1); + } + return a; +} diff --git a/compiler/test/parse-err/loop-until-non-bool.gr b/compiler/test/parse-err/loop-until-non-bool.gr new file mode 100644 index 0000000..84f01d2 --- /dev/null +++ b/compiler/test/parse-err/loop-until-non-bool.gr @@ -0,0 +1,10 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LoopUntilNonBool() -> int { + a = int(0); + for i in int(1):int(10) { + a = a + int(1); + // expected-error@below{{loop condition does not produce a boolean scalar, got int}} + } until int(5); + return a; +} diff --git a/compiler/test/parse-err/return-in-loop.gr b/compiler/test/parse-err/return-in-loop.gr new file mode 100644 index 0000000..ddfb472 --- /dev/null +++ b/compiler/test/parse-err/return-in-loop.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func ReturnInLoop() -> int { + for i in int(1):int(10) { + // expected-error@below{{return statement inside a loop is not allowed}} + return int(5); + } + return int(0); +} diff --git a/compiler/test/parse-err/return-missing.gr b/compiler/test/parse-err/return-missing.gr new file mode 100644 index 0000000..d153997 --- /dev/null +++ b/compiler/test/parse-err/return-missing.gr @@ -0,0 +1,6 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-error@below{{function must have a return statement}} +func ReturnMissing() -> int { + a = int(42); +} diff --git a/compiler/test/parse-err/return-multiple.gr b/compiler/test/parse-err/return-multiple.gr new file mode 100644 index 0000000..68d7074 --- /dev/null +++ b/compiler/test/parse-err/return-multiple.gr @@ -0,0 +1,8 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func ReturnMultiple() -> int { + a = int(42); + return a; + // expected-error@below{{statement after return is not allowed}} + return int(5); +} diff --git a/compiler/test/parse-err/return-not-last.gr b/compiler/test/parse-err/return-not-last.gr new file mode 100644 index 0000000..22109dc --- /dev/null +++ b/compiler/test/parse-err/return-not-last.gr @@ -0,0 +1,8 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func ReturnNotLast() -> int { + return int(42); + // expected-error@below{{statement after return is not allowed}} + a = int(5); + return a; +} diff --git a/compiler/test/parse-err/return-wrong-type.gr b/compiler/test/parse-err/return-wrong-type.gr new file mode 100644 index 0000000..7638cb9 --- /dev/null +++ b/compiler/test/parse-err/return-wrong-type.gr @@ -0,0 +1,6 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func ReturnWrongType() -> int { + // expected-error@below{{return type mismatch: expected int, but got real}} + return real(3.14); +} diff --git a/docs/parse-tests.md b/docs/parse-tests.md new file mode 100644 index 0000000..4bb066a --- /dev/null +++ b/docs/parse-tests.md @@ -0,0 +1,325 @@ +# Parser Testing Guide + +This guide explains how to write and test parser error tests for the GraphAlg compiler. + +## Test Framework + +The GraphAlg compiler uses **LLVM's `lit` (LLVM Integrated Tester)** framework for testing. Tests are located in `compiler/test/`. + +### Test Types + +1. **Success tests** (`compiler/test/parse/`) - Verify correct parsing and MLIR output using FileCheck +2. **Error tests** (`compiler/test/parse-err/`) - Verify that invalid code produces expected error messages + +## Writing Parser Error Tests + +### Basic Structure + +```graphalg +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func MyTest() -> int { + // expected-error@below{{error message}} + invalid code here; + return int(0); +} +``` + +### Key Components + +1. **RUN directive**: Specifies how to run the test + - `--verify-diagnostics` flag enables diagnostic verification + - `%s` is replaced with the test file path + +2. **Diagnostic annotations**: Tell the verifier what errors/notes to expect + - `// expected-error@below{...}` - expects an error on the next line + - `// expected-note@below{...}` - expects a note/additional info on the next line + - Messages in braces must match the actual diagnostic message exactly + +3. **Error location**: The verifier checks that errors appear at the expected location + +### Multiple Diagnostics + +When multiple diagnostics appear on the same statement, list them all: + +```graphalg +// expected-error@below{{base dimensions do not match the dimensions of the mask}} +// expected-note@below{{base dimension: (s x s)}} +// expected-note@below{{mask dimensions: (t x t)}} +a = e; +``` + +## Running Tests + +### Run All Tests + +```bash +cmake --build compiler/build --target check +``` + +### Run Individual Tests + +```bash +# First build the compiler +cmake --build compiler/build --target graphalg-translate + +# Run with diagnostic verification +./compiler/build/tools/graphalg-translate --import-graphalg --verify-diagnostics path/to/test.gr + +# Run without verification to see actual errors +./compiler/build/tools/graphalg-translate --import-graphalg path/to/test.gr +``` + +## Common Error Categories + +### 1. Duplicate Definitions + +**Function names** (`func-name-dup.gr`): +```graphalg +// expected-note@below{{original definition here}} +func Dup(a: int) -> int { return a; } + +// expected-error@below{{duplicate definition of function 'Dup'}} +func Dup(a: int) -> int { return a; } +``` + +**Parameter names** (`func-param-dup.gr`): +```graphalg +func Dup( + // expected-note@below{{previous definition here}} + a: int, + // expected-error@below{{duplicate parameter name 'a'}} + a: int) -> int { return a; } +``` + +### 2. Fill Syntax Errors + +**Vector fill on non-vector** (`vector-fill-row-vector.gr`): +```graphalg +func Test(m: Matrix<1, s, int>, x: int) -> Matrix<1, s, int> { + // expected-error@below{{vector fill [:] used with non-vector base}} + // expected-note@below{{base has type Matrix<1, s, int>}} + m[:] = x; + return m; +} +``` + +**Matrix fill on vector** (`matrix-fill-col-vector.gr`): +```graphalg +func Test(v: Vector, x: int) -> Vector { + // expected-error@below{{matrix fill [:, :] used with column vector base}} + // expected-note@below{{base has type Vector}} + v[:, :] = x; + return v; +} +``` + +**Non-scalar fill expression** (`vector-fill-non-scalar.gr`): +```graphalg +func Test(v: Vector, e: Vector) -> Vector { + // expected-error@below{{fill expression is not a scalar}} + v[:] = e; + return v; +} +``` + +### 3. Masked Assignment Errors + +**Dimension mismatch** (`mask-dimension-mismatch.gr`): +```graphalg +func Test(a: Matrix, m: Matrix, e: Matrix) -> Matrix { + // expected-error@below{{base dimensions do not match the dimensions of the mask}} + // expected-note@below{{base dimension: (s x s)}} + // expected-note@below{{mask dimensions: (t x t)}} + a = e; + return a; +} +``` + +### 4. Type Errors + +**Reassignment with different type** (`reassign-type-mismatch.gr`): +```graphalg +func Test() -> int { + // expected-note@below{{previous assigment was here}} + a = int(42); + // expected-error@below{{cannot assign value of type real to previously defined variable of type int}} + a = real(3.14); + return int(0); +} +``` + +**Accumulate type mismatch** (`accum-type-mismatch.gr`): +```graphalg +func Test() -> int { + a = int(42); + // expected-error@below{{type of base does not match the expression to accumulate: (int vs. real}} + a += real(3.14); + return int(0); +} +``` + +### 5. Variable Scoping + +**Undefined variable** (`accum-undefined.gr`): +```graphalg +func Test() -> int { + // expected-error@below{{undefined variable}} + a += int(42); + return int(0); +} +``` + +**Loop scope** (`loop-scope.gr`): +```graphalg +func Test() -> int { + a = int(0); + for i in int(1):int(10) { + b = int(42); + a = a + b; + } + // Variable b is not accessible outside the loop + // expected-error@below{{unrecognized variable}} + return b; +} +``` + +### 6. For Loop Errors + +**Non-integer range bounds** (`loop-range-start-non-int.gr`, `loop-range-end-non-int.gr`): +```graphalg +func Test() -> int { + a = int(0); + // expected-error@below{{loop range start must be an integer, but got real}} + for i in real(1.0):int(10) { + a = a + int(1); + } + return a; +} +``` + +**Non-dimension range** (`loop-range-not-dimension.gr`): +```graphalg +func Test(m: Matrix) -> int { + a = int(0); + // expected-error@below{{not a dimension type}} + // expected-note@below{{defined here}} + for i in m.nvals { + a = a + int(1); + } + return a; +} +``` + +**Non-boolean until condition** (`loop-until-non-bool.gr`): +```graphalg +func Test() -> int { + a = int(0); + for i in int(1):int(10) { + a = a + int(1); + // expected-error@below{{loop condition does not produce a boolean scalar, got int}} + } until int(5); + return a; +} +``` + +## Type Formatting in Error Messages + +The parser formats types in a user-friendly way: +- Scalars: `int`, `real`, `bool`, `trop_int`, `trop_real` +- Vectors: `Vector` (column vector with dimension `s` and element type `int`) +- Matrices: `Matrix` (matrix with row dimension `r`, column dimension `c`, and element type `int`) + +## Tips for Writing Tests + +1. **Test one error at a time** - Keep tests focused on a single error condition +2. **Use descriptive function names** - Name the function after what it tests +3. **Add comments** - Explain what the test is checking +4. **Verify exact error messages** - The diagnostic message must match exactly +5. **Check location precision** - Ensure the error points to the right token +6. **Test boundary cases** - Cover edge cases like row vectors, column vectors, and full matrices + +## Adding Parser Error Checks + +When adding new error detection to the parser: + +1. **Identify the error condition** in the parser code +2. **Choose an appropriate error message** - Be clear and user-friendly +3. **Use `mlir::emitError(location)`** to report the error +4. **Add notes with `diag.attachNote()`** for additional context +5. **Write a test** that verifies the error is caught +6. **Run the test** to verify the exact error message format +7. **Update the test** with the correct expected message + +### Example: Adding Loop Range Type Checks + +```cpp +// Check that begin is an integer scalar +auto intScalarType = MatrixType::scalarOf(SemiringTypes::forInt(_builder.getContext())); +if (r.begin.getType() != intScalarType) { + return mlir::emitError(beginLoc) + << "loop range start must be an integer, but got " + << typeToString(r.begin.getType()); +} +``` + +### 7. Return Statement Errors + +**Return inside loop** (`return-in-loop.gr`): +```graphalg +func Test() -> int { + for i in int(1):int(10) { + // expected-error@below{{return statement inside a loop is not allowed}} + return int(5); + } + return int(0); +} +``` + +**Return not last statement** (`return-not-last.gr`): +```graphalg +func Test() -> int { + return int(42); + // expected-error@below{{statement after return is not allowed}} + a = int(5); + return a; +} +``` + +**Return wrong type** (`return-wrong-type.gr`): +```graphalg +func Test() -> int { + // expected-error@below{{return type mismatch: expected int, but got real}} + return real(3.14); +} +``` + +**Multiple return statements** (`return-multiple.gr`): +```graphalg +func Test() -> int { + a = int(42); + return a; + // expected-error@below{{statement after return is not allowed}} + return int(5); +} +``` + +**Missing return statement** (`return-missing.gr`): +```graphalg +// expected-error@below{{function must have a return statement}} +func Test() -> int { + a = int(42); +} +``` + +## Current Test Coverage + +As of now, we have 136 parser tests covering: +- Duplicate definitions (functions and parameters) +- Fill syntax errors (vector vs matrix, non-scalar expressions) +- Masked assignment errors (dimension mismatches) +- Variable reassignment errors (type mismatches) +- Accumulate errors (undefined variable, type mismatches) +- Variable scoping (loop-local variables) +- For loop errors (non-integer start/end, non-dimension range, non-boolean until condition) +- Return statement errors (in loop, not last, wrong type, multiple returns, missing return) From c6824328c3ef40689e191f034e5b7a281d8929a0 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 25 Nov 2025 16:37:00 +0000 Subject: [PATCH 4/9] More tests. --- compiler/src/graphalg/parse/Parser.cpp | 208 +++++++++++++++++- .../apply-binary-second-arg-not-scalar.gr | 14 ++ .../parse-err/apply-func-non-scalar-args.gr | 12 + .../apply-unary-func-wrong-arg-count.gr | 12 + .../test/parse-err/apply-undefined-func.gr | 7 + compiler/test/parse-err/diag-not-vector.gr | 9 + .../parse-err/ewise-different-dimensions.gr | 11 + .../parse-err/ewise-different-semirings.gr | 11 + .../parse-err/matmul-dimension-mismatch.gr | 12 + .../test/parse-err/neg-bool-unsupported.gr | 9 + .../parse-err/neg-trop-int-unsupported.gr | 9 + .../select-binary-func-non-bool-return.gr | 14 ++ .../select-binary-second-arg-not-scalar.gr | 14 ++ .../parse-err/select-func-non-bool-return.gr | 12 + .../parse-err/select-func-non-scalar-args.gr | 12 + .../select-unary-func-wrong-arg-count.gr | 12 + .../test/parse-err/select-undefined-func.gr | 7 + .../test/parse-err/sub-bool-unsupported.gr | 9 + .../test/parse-err/sub-matrix-not-scalar.gr | 11 + .../parse-err/sub-trop-int-unsupported.gr | 9 + compiler/test/parse-err/undefined-var.gr | 6 + compiler/test/parse/diag-scalar.gr | 8 + docs/parse-tests.md | 140 +++++++++++- 23 files changed, 564 insertions(+), 4 deletions(-) create mode 100644 compiler/test/parse-err/apply-binary-second-arg-not-scalar.gr create mode 100644 compiler/test/parse-err/apply-func-non-scalar-args.gr create mode 100644 compiler/test/parse-err/apply-unary-func-wrong-arg-count.gr create mode 100644 compiler/test/parse-err/apply-undefined-func.gr create mode 100644 compiler/test/parse-err/diag-not-vector.gr create mode 100644 compiler/test/parse-err/ewise-different-dimensions.gr create mode 100644 compiler/test/parse-err/ewise-different-semirings.gr create mode 100644 compiler/test/parse-err/matmul-dimension-mismatch.gr create mode 100644 compiler/test/parse-err/neg-bool-unsupported.gr create mode 100644 compiler/test/parse-err/neg-trop-int-unsupported.gr create mode 100644 compiler/test/parse-err/select-binary-func-non-bool-return.gr create mode 100644 compiler/test/parse-err/select-binary-second-arg-not-scalar.gr create mode 100644 compiler/test/parse-err/select-func-non-bool-return.gr create mode 100644 compiler/test/parse-err/select-func-non-scalar-args.gr create mode 100644 compiler/test/parse-err/select-unary-func-wrong-arg-count.gr create mode 100644 compiler/test/parse-err/select-undefined-func.gr create mode 100644 compiler/test/parse-err/sub-bool-unsupported.gr create mode 100644 compiler/test/parse-err/sub-matrix-not-scalar.gr create mode 100644 compiler/test/parse-err/sub-trop-int-unsupported.gr create mode 100644 compiler/test/parse-err/undefined-var.gr create mode 100644 compiler/test/parse/diag-scalar.gr diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index 5246ba1..91715d9 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -180,6 +180,9 @@ class Parser { mlir::Value buildMatMul(mlir::Location loc, mlir::Value lhs, mlir::Value rhs); + mlir::Value buildElementWise(mlir::Location loc, mlir::Value lhs, BinaryOp op, + mlir::Value rhs, bool isExplicitElementWise = false); + mlir::ParseResult parseAtom(mlir::Value &v); mlir::ParseResult parseLiteral(mlir::Type ring, mlir::Value &v); @@ -1155,7 +1158,10 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { return mlir::failure(); } - atomLhs = _builder.create(loc, atomLhs, binop, atomRhs); + atomLhs = buildElementWise(loc, atomLhs, binop, atomRhs, true); + if (!atomLhs) { + return mlir::failure(); + } } } else { // Binary operator @@ -1169,9 +1175,14 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { if (binop == BinaryOp::MUL) { // Matmul special case atomLhs = buildMatMul(loc, atomLhs, atomRhs); + if (!atomLhs) { + return mlir::failure(); + } } else { - // TODO: check scalar matrix types - atomLhs = _builder.create(loc, atomLhs, binop, atomRhs); + atomLhs = buildElementWise(loc, atomLhs, binop, atomRhs); + if (!atomLhs) { + return mlir::failure(); + } } } } @@ -1251,6 +1262,64 @@ mlir::Value Parser::buildMatMul(mlir::Location loc, mlir::Value lhs, return nullptr; } +mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, + BinaryOp op, mlir::Value rhs, + bool isExplicitElementWise) { + auto lhsType = llvm::cast(lhs.getType()); + auto rhsType = llvm::cast(rhs.getType()); + + // Check that semirings match + if (lhsType.getSemiring() != rhsType.getSemiring()) { + auto diag = mlir::emitError(loc) + << "element-wise operation requires operands to have the same semiring"; + diag.attachNote(lhs.getLoc()) + << "left operand has semiring " << typeToString(lhsType.getSemiring()); + diag.attachNote(rhs.getLoc()) + << "right operand has semiring " << typeToString(rhsType.getSemiring()); + return nullptr; + } + + // Check that dimensions match + if (lhsType.getDims() != rhsType.getDims()) { + auto diag = mlir::emitError(loc) + << "element-wise operation requires operands to have the same dimensions"; + diag.attachNote(lhs.getLoc()) + << "left operand has dimensions " << dimsToString(lhsType.getDims()); + diag.attachNote(rhs.getLoc()) + << "right operand has dimensions " << dimsToString(rhsType.getDims()); + return nullptr; + } + + // Special checks for subtraction + if (op == BinaryOp::SUB) { + // Regular subtraction (e1 - e2) only works on scalars + // Element-wise subtraction (e1 (.-) e2) works on any dimensions + if (!isExplicitElementWise && (!lhsType.isScalar() || !rhsType.isScalar())) { + auto diag = mlir::emitError(loc) + << "subtraction only works on scalars; use element-wise subtraction (.-) for matrices"; + diag.attachNote(lhs.getLoc()) + << "left operand has type " << typeToString(lhsType); + diag.attachNote(rhs.getLoc()) + << "right operand has type " << typeToString(rhsType); + return nullptr; + } + + // Subtraction (both forms) only supports int and real semirings + auto *ctx = _builder.getContext(); + auto semiring = lhsType.getSemiring(); + if (semiring != SemiringTypes::forInt(ctx) && + semiring != SemiringTypes::forReal(ctx)) { + auto diag = mlir::emitError(loc) + << "subtraction is only supported for int and real types"; + diag.attachNote(lhs.getLoc()) + << "operands have semiring " << typeToString(semiring); + return nullptr; + } + } + + return _builder.create(loc, lhs, op, rhs); +} + mlir::ParseResult Parser::parseAtom(mlir::Value &v) { auto loc = cur().loc; switch (cur().type) { @@ -1396,6 +1465,52 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { return mlir::failure(); } + // Validate function signature + auto funcType = func.getFunctionType(); + size_t numFuncArgs = funcType.getNumInputs(); + + if (args.size() == 1) { + // Unary apply: function must have exactly 1 argument + if (numFuncArgs != 1) { + auto diag = mlir::emitError(loc) + << "apply() with 1 matrix argument requires a function with 1 parameter, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + } else { + // Binary apply: function must have exactly 2 arguments + if (numFuncArgs != 2) { + auto diag = mlir::emitError(loc) + << "apply() with 2 arguments requires a function with 2 parameters, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + + // Second argument must be a scalar + auto arg1Type = llvm::cast(args[1].getType()); + if (!arg1Type.isScalar()) { + auto diag = mlir::emitError(loc) + << "second argument to apply() must be a scalar"; + diag.attachNote(args[1].getLoc()) + << "argument has type " << typeToString(arg1Type); + return mlir::failure(); + } + } + + // Check that all function parameters are scalars + for (size_t i = 0; i < numFuncArgs; i++) { + auto paramType = llvm::dyn_cast(funcType.getInput(i)); + if (!paramType || !paramType.isScalar()) { + auto diag = mlir::emitError(loc) + << "apply() requires function parameters to be scalars"; + diag.attachNote(func.getLoc()) + << "parameter " << i << " has type " << funcType.getInput(i); + return mlir::failure(); + } + } + if (args.size() == 1) { v = _builder.create(loc, func, args[0]); } else { @@ -1426,6 +1541,71 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { return mlir::failure(); } + // Validate function signature + auto funcType = func.getFunctionType(); + size_t numFuncArgs = funcType.getNumInputs(); + + if (args.size() == 1) { + // Unary select: function must have exactly 1 argument + if (numFuncArgs != 1) { + auto diag = mlir::emitError(loc) + << "select() with 1 matrix argument requires a function with 1 parameter, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + } else { + // Binary select: function must have exactly 2 arguments + if (numFuncArgs != 2) { + auto diag = mlir::emitError(loc) + << "select() with 2 arguments requires a function with 2 parameters, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + + // Second argument must be a scalar + auto arg1Type = llvm::cast(args[1].getType()); + if (!arg1Type.isScalar()) { + auto diag = mlir::emitError(loc) + << "second argument to select() must be a scalar"; + diag.attachNote(args[1].getLoc()) + << "argument has type " << typeToString(arg1Type); + return mlir::failure(); + } + } + + // Check that all function parameters are scalars + for (size_t i = 0; i < numFuncArgs; i++) { + auto paramType = llvm::dyn_cast(funcType.getInput(i)); + if (!paramType || !paramType.isScalar()) { + auto diag = mlir::emitError(loc) + << "select() requires function parameters to be scalars"; + diag.attachNote(func.getLoc()) + << "parameter " << i << " has type " << funcType.getInput(i); + return mlir::failure(); + } + } + + // Check that function returns a boolean scalar + if (funcType.getNumResults() != 1) { + auto diag = mlir::emitError(loc) + << "select() requires function to return exactly one value"; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + + auto returnType = llvm::dyn_cast(funcType.getResult(0)); + auto *ctx = _builder.getContext(); + auto expectedReturnType = MatrixType::scalarOf(SemiringTypes::forBool(ctx)); + if (!returnType || returnType != expectedReturnType) { + auto diag = mlir::emitError(loc) + << "select() requires function to return bool"; + diag.attachNote(func.getLoc()) + << "function returns " << funcType.getResult(0); + return mlir::failure(); + } + if (args.size() == 1) { v = _builder.create(loc, func.getSymName(), args[0]); } else { @@ -1499,6 +1679,15 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { return mlir::failure(); } + auto argType = llvm::cast(arg.getType()); + if (!argType.isColumnVector() && !argType.isRowVector()) { + auto diag = mlir::emitError(loc) + << "diag() requires a row or column vector"; + diag.attachNote(arg.getLoc()) + << "argument has type " << typeToString(argType); + return mlir::failure(); + } + v = _builder.create(loc, arg); return mlir::success(); } @@ -1548,6 +1737,19 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { return mlir::failure(); } + // Check that negation is only used with int or real semirings + auto vType = llvm::cast(v.getType()); + auto semiring = vType.getSemiring(); + auto *ctx = _builder.getContext(); + if (semiring != SemiringTypes::forInt(ctx) && + semiring != SemiringTypes::forReal(ctx)) { + auto diag = mlir::emitError(loc) + << "negation is only supported for int and real types"; + diag.attachNote(v.getLoc()) + << "operand has semiring " << typeToString(semiring); + return mlir::failure(); + } + v = _builder.create(loc, v); return mlir::success(); } diff --git a/compiler/test/parse-err/apply-binary-second-arg-not-scalar.gr b/compiler/test/parse-err/apply-binary-second-arg-not-scalar.gr new file mode 100644 index 0000000..fd6e998 --- /dev/null +++ b/compiler/test/parse-err/apply-binary-second-arg-not-scalar.gr @@ -0,0 +1,14 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func binary(a: int, b: int) -> int { + return a + b; +} + +func ApplyBinarySecondArgNotScalar( + a: Matrix, + // expected-note@below{{argument has type Vector}} + b: Vector) -> Matrix { + // Binary apply requires the second argument to be a scalar + // expected-error@below{{second argument to apply() must be a scalar}} + return apply(binary, a, b); +} diff --git a/compiler/test/parse-err/apply-func-non-scalar-args.gr b/compiler/test/parse-err/apply-func-non-scalar-args.gr new file mode 100644 index 0000000..e2675c9 --- /dev/null +++ b/compiler/test/parse-err/apply-func-non-scalar-args.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{parameter 0 has type '!graphalg.mat x distinct[0]<> x i64>'}} +func nonScalarFunc(m: Matrix) -> int { + return int(0); +} + +func ApplyFuncNonScalarArgs(m: Matrix) -> Matrix { + // apply() requires the function to have scalar arguments + // expected-error@below{{apply() requires function parameters to be scalars}} + return apply(nonScalarFunc, m); +} diff --git a/compiler/test/parse-err/apply-unary-func-wrong-arg-count.gr b/compiler/test/parse-err/apply-unary-func-wrong-arg-count.gr new file mode 100644 index 0000000..cdb32cc --- /dev/null +++ b/compiler/test/parse-err/apply-unary-func-wrong-arg-count.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{function defined here}} +func twoArgs(a: int, b: int) -> int { + return a + b; +} + +func ApplyUnaryFuncWrongArgCount(m: Matrix) -> Matrix { + // Unary apply requires the function to have exactly 1 argument + // expected-error@below{{apply() with 1 matrix argument requires a function with 1 parameter, but got 2}} + return apply(twoArgs, m); +} diff --git a/compiler/test/parse-err/apply-undefined-func.gr b/compiler/test/parse-err/apply-undefined-func.gr new file mode 100644 index 0000000..697f55f --- /dev/null +++ b/compiler/test/parse-err/apply-undefined-func.gr @@ -0,0 +1,7 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func ApplyUndefinedFunc(m: Matrix) -> Matrix { + // The function 'undefinedFunc' does not exist + // expected-error@below{{unknown function 'undefinedFunc'}} + return apply(undefinedFunc, m); +} diff --git a/compiler/test/parse-err/diag-not-vector.gr b/compiler/test/parse-err/diag-not-vector.gr new file mode 100644 index 0000000..81297cc --- /dev/null +++ b/compiler/test/parse-err/diag-not-vector.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func DiagNotVector( + // expected-note@below{{argument has type Matrix}} + m: Matrix) -> Matrix { + // diag() requires a row or column vector, but m is a general matrix + // expected-error@below{{diag() requires a row or column vector}} + return diag(m); +} diff --git a/compiler/test/parse-err/ewise-different-dimensions.gr b/compiler/test/parse-err/ewise-different-dimensions.gr new file mode 100644 index 0000000..1183146 --- /dev/null +++ b/compiler/test/parse-err/ewise-different-dimensions.gr @@ -0,0 +1,11 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func EwiseDifferentDimensions( + // expected-note@below{{left operand has dimensions (s x s)}} + a: Matrix, + // expected-note@below{{right operand has dimensions (t x t)}} + b: Matrix) -> Matrix { + // Element-wise operations require operands to have the same dimensions + // expected-error@below{{element-wise operation requires operands to have the same dimensions}} + return a (.+) b; +} diff --git a/compiler/test/parse-err/ewise-different-semirings.gr b/compiler/test/parse-err/ewise-different-semirings.gr new file mode 100644 index 0000000..6e36446 --- /dev/null +++ b/compiler/test/parse-err/ewise-different-semirings.gr @@ -0,0 +1,11 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func EwiseDifferentSemirings( + // expected-note@below{{left operand has semiring int}} + a: Matrix, + // expected-note@below{{right operand has semiring real}} + b: Matrix) -> Matrix { + // Element-wise operations require operands to have the same semiring + // expected-error@below{{element-wise operation requires operands to have the same semiring}} + return a (.+) b; +} diff --git a/compiler/test/parse-err/matmul-dimension-mismatch.gr b/compiler/test/parse-err/matmul-dimension-mismatch.gr new file mode 100644 index 0000000..cc6a5c8 --- /dev/null +++ b/compiler/test/parse-err/matmul-dimension-mismatch.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func MatMulDimensionMismatch( + // expected-note@below{{left side has dimensions (r x s)}} + a: Matrix, + // expected-note@below{{right side has dimensions (t x u)}} + b: Matrix) -> Matrix { + // Left matrix has s columns, right matrix has t rows + // These dimensions must match for matrix multiplication + // expected-error@below{{incompatible dimensions for matrix multiply}} + return a * b; +} diff --git a/compiler/test/parse-err/neg-bool-unsupported.gr b/compiler/test/parse-err/neg-bool-unsupported.gr new file mode 100644 index 0000000..7ec17e5 --- /dev/null +++ b/compiler/test/parse-err/neg-bool-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func NegBoolUnsupported( + // expected-note@below{{operand has semiring bool}} + a: bool) -> bool { + // Negation is not supported for bool semiring (only int and real) + // expected-error@below{{negation is only supported for int and real types}} + return -a; +} diff --git a/compiler/test/parse-err/neg-trop-int-unsupported.gr b/compiler/test/parse-err/neg-trop-int-unsupported.gr new file mode 100644 index 0000000..a841c6b --- /dev/null +++ b/compiler/test/parse-err/neg-trop-int-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func NegTropIntUnsupported( + // expected-note@below{{operand has semiring trop_int}} + a: trop_int) -> trop_int { + // Negation is not supported for tropical semirings (only int and real) + // expected-error@below{{negation is only supported for int and real types}} + return -a; +} diff --git a/compiler/test/parse-err/select-binary-func-non-bool-return.gr b/compiler/test/parse-err/select-binary-func-non-bool-return.gr new file mode 100644 index 0000000..dc07b83 --- /dev/null +++ b/compiler/test/parse-err/select-binary-func-non-bool-return.gr @@ -0,0 +1,14 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{function returns '!graphalg.mat<1 x 1 x f64>'}} +func returnsBinaryReal(a: int, b: int) -> real { + return real(3.14); +} + +func SelectBinaryFuncNonBoolReturn( + a: Matrix, + b: int) -> Matrix { + // Binary select also requires the function to return bool + // expected-error@below{{select() requires function to return bool}} + return select(returnsBinaryReal, a, b); +} diff --git a/compiler/test/parse-err/select-binary-second-arg-not-scalar.gr b/compiler/test/parse-err/select-binary-second-arg-not-scalar.gr new file mode 100644 index 0000000..57fb340 --- /dev/null +++ b/compiler/test/parse-err/select-binary-second-arg-not-scalar.gr @@ -0,0 +1,14 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func binary(a: int, b: int) -> bool { + return a == b; +} + +func SelectBinarySecondArgNotScalar( + a: Matrix, + // expected-note@below{{argument has type Vector}} + b: Vector) -> Matrix { + // Binary select requires the second argument to be a scalar + // expected-error@below{{second argument to select() must be a scalar}} + return select(binary, a, b); +} diff --git a/compiler/test/parse-err/select-func-non-bool-return.gr b/compiler/test/parse-err/select-func-non-bool-return.gr new file mode 100644 index 0000000..c5669a9 --- /dev/null +++ b/compiler/test/parse-err/select-func-non-bool-return.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{function returns '!graphalg.mat<1 x 1 x i64>'}} +func returnsInt(a: int) -> int { + return a; +} + +func SelectFuncNonBoolReturn(m: Matrix) -> Matrix { + // select() requires the function to return bool + // expected-error@below{{select() requires function to return bool}} + return select(returnsInt, m); +} diff --git a/compiler/test/parse-err/select-func-non-scalar-args.gr b/compiler/test/parse-err/select-func-non-scalar-args.gr new file mode 100644 index 0000000..08340ea --- /dev/null +++ b/compiler/test/parse-err/select-func-non-scalar-args.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{parameter 0 has type '!graphalg.mat x distinct[0]<> x i64>'}} +func nonScalarFunc(m: Matrix) -> bool { + return bool(true); +} + +func SelectFuncNonScalarArgs(m: Matrix) -> Matrix { + // select() requires the function to have scalar arguments + // expected-error@below{{select() requires function parameters to be scalars}} + return select(nonScalarFunc, m); +} diff --git a/compiler/test/parse-err/select-unary-func-wrong-arg-count.gr b/compiler/test/parse-err/select-unary-func-wrong-arg-count.gr new file mode 100644 index 0000000..0eb9905 --- /dev/null +++ b/compiler/test/parse-err/select-unary-func-wrong-arg-count.gr @@ -0,0 +1,12 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{function defined here}} +func twoArgs(a: int, b: int) -> bool { + return a == b; +} + +func SelectUnaryFuncWrongArgCount(m: Matrix) -> Matrix { + // Unary select requires the function to have exactly 1 argument + // expected-error@below{{select() with 1 matrix argument requires a function with 1 parameter, but got 2}} + return select(twoArgs, m); +} diff --git a/compiler/test/parse-err/select-undefined-func.gr b/compiler/test/parse-err/select-undefined-func.gr new file mode 100644 index 0000000..9746575 --- /dev/null +++ b/compiler/test/parse-err/select-undefined-func.gr @@ -0,0 +1,7 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func SelectUndefinedFunc(m: Matrix) -> Matrix { + // The function 'undefinedFunc' does not exist + // expected-error@below{{unknown function 'undefinedFunc'}} + return select(undefinedFunc, m); +} diff --git a/compiler/test/parse-err/sub-bool-unsupported.gr b/compiler/test/parse-err/sub-bool-unsupported.gr new file mode 100644 index 0000000..f0d9d83 --- /dev/null +++ b/compiler/test/parse-err/sub-bool-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func SubBoolUnsupported( + // expected-note@below{{operands have semiring bool}} + a: bool, b: bool) -> bool { + // Subtraction is not supported for bool semiring (only int and real) + // expected-error@below{{subtraction is only supported for int and real types}} + return a - b; +} diff --git a/compiler/test/parse-err/sub-matrix-not-scalar.gr b/compiler/test/parse-err/sub-matrix-not-scalar.gr new file mode 100644 index 0000000..0e1f46c --- /dev/null +++ b/compiler/test/parse-err/sub-matrix-not-scalar.gr @@ -0,0 +1,11 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func SubMatrixNotScalar( + // expected-note@below{{left operand has type Matrix}} + a: Matrix, + // expected-note@below{{right operand has type Matrix}} + b: Matrix) -> Matrix { + // Subtraction only works on scalars, not matrices + // expected-error@below{{subtraction only works on scalars; use element-wise subtraction (.-) for matrices}} + return a - b; +} diff --git a/compiler/test/parse-err/sub-trop-int-unsupported.gr b/compiler/test/parse-err/sub-trop-int-unsupported.gr new file mode 100644 index 0000000..b8f0ce8 --- /dev/null +++ b/compiler/test/parse-err/sub-trop-int-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func SubTropIntUnsupported( + // expected-note@below{{operands have semiring trop_int}} + a: trop_int, b: trop_int) -> trop_int { + // Subtraction is not supported for tropical semirings (only int and real) + // expected-error@below{{subtraction is only supported for int and real types}} + return a - b; +} diff --git a/compiler/test/parse-err/undefined-var.gr b/compiler/test/parse-err/undefined-var.gr new file mode 100644 index 0000000..b7931e8 --- /dev/null +++ b/compiler/test/parse-err/undefined-var.gr @@ -0,0 +1,6 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func UndefinedVar() -> int { + // expected-error@below{{unrecognized variable}} + return i_am_undefined; +} diff --git a/compiler/test/parse/diag-scalar.gr b/compiler/test/parse/diag-scalar.gr new file mode 100644 index 0000000..14e7328 --- /dev/null +++ b/compiler/test/parse/diag-scalar.gr @@ -0,0 +1,8 @@ +// RUN: graphalg-translate --import-graphalg < %s | FileCheck %s + +// Scalars (1x1 matrices) are both row and column vectors, so diag should accept them +// CHECK-LABEL: @DiagScalar +func DiagScalar(x: int) -> int { + // CHECK: graphalg.diag %arg0 + return diag(x); +} diff --git a/docs/parse-tests.md b/docs/parse-tests.md index 4bb066a..3ed0c8f 100644 --- a/docs/parse-tests.md +++ b/docs/parse-tests.md @@ -312,9 +312,135 @@ func Test() -> int { } ``` +### 8. Matrix Multiplication Errors + +**Dimension mismatch** (`matmul-dimension-mismatch.gr`): +```graphalg +func MatMulDimensionMismatch( + // expected-note@below{{left side has dimensions (r x s)}} + a: Matrix, + // expected-note@below{{right side has dimensions (t x u)}} + b: Matrix) -> Matrix { + // expected-error@below{{incompatible dimensions for matrix multiply}} + return a * b; +} +``` + +### 9. Built-in Function Errors + +**diag() with non-vector** (`diag-not-vector.gr`): +```graphalg +func DiagNotVector( + // expected-note@below{{argument has type Matrix}} + m: Matrix) -> Matrix { + // expected-error@below{{diag() requires a row or column vector}} + return diag(m); +} +``` + +**apply() with wrong function signature** (`apply-unary-func-wrong-arg-count.gr`): +```graphalg +// expected-note@below{{function defined here}} +func twoArgs(a: int, b: int) -> int { + return a + b; +} + +func ApplyUnaryFuncWrongArgCount(m: Matrix) -> Matrix { + // expected-error@below{{apply() with 1 matrix argument requires a function with 1 parameter, but got 2}} + return apply(twoArgs, m); +} +``` + +**apply() with non-scalar function parameters** (`apply-func-non-scalar-args.gr`): +```graphalg +// expected-note@below{{parameter 0 has type '!graphalg.mat x distinct[0]<> x i64>'}} +func nonScalarFunc(m: Matrix) -> int { + return int(0); +} + +func ApplyFuncNonScalarArgs(m: Matrix) -> Matrix { + // expected-error@below{{apply() requires function parameters to be scalars}} + return apply(nonScalarFunc, m); +} +``` + +**select() with non-bool return type** (`select-func-non-bool-return.gr`): +```graphalg +// expected-note@below{{function returns '!graphalg.mat<1 x 1 x i64>'}} +func returnsInt(a: int) -> int { + return a; +} + +func SelectFuncNonBoolReturn(m: Matrix) -> Matrix { + // expected-error@below{{select() requires function to return bool}} + return select(returnsInt, m); +} +``` + +### 10. Element-wise Operation Errors + +**Different semirings** (`ewise-different-semirings.gr`): +```graphalg +func EwiseDifferentSemirings( + // expected-note@below{{left operand has semiring int}} + a: Matrix, + // expected-note@below{{right operand has semiring real}} + b: Matrix) -> Matrix { + // expected-error@below{{element-wise operation requires operands to have the same semiring}} + return a (.+) b; +} +``` + +**Different dimensions** (`ewise-different-dimensions.gr`): +```graphalg +func EwiseDifferentDimensions( + // expected-note@below{{left operand has dimensions (s x s)}} + a: Matrix, + // expected-note@below{{right operand has dimensions (t x t)}} + b: Matrix) -> Matrix { + // expected-error@below{{element-wise operation requires operands to have the same dimensions}} + return a (.+) b; +} +``` + +### 11. Subtraction and Negation Errors + +**Subtraction with unsupported semiring** (`sub-bool-unsupported.gr`, `sub-trop-int-unsupported.gr`): +```graphalg +func SubBoolUnsupported( + // expected-note@below{{operands have semiring bool}} + a: bool, b: bool) -> bool { + // expected-error@below{{subtraction is only supported for int and real types}} + return a - b; +} +``` + +**Subtraction with non-scalars** (`sub-matrix-not-scalar.gr`): +```graphalg +func SubMatrixNotScalar( + // expected-note@below{{left operand has type Matrix}} + a: Matrix, + // expected-note@below{{right operand has type Matrix}} + b: Matrix) -> Matrix { + // Note: Use element-wise subtraction (.-) for matrices + // expected-error@below{{subtraction only works on scalars; use element-wise subtraction (.-) for matrices}} + return a - b; +} +``` + +**Negation with unsupported semiring** (`neg-bool-unsupported.gr`, `neg-trop-int-unsupported.gr`): +```graphalg +func NegBoolUnsupported( + // expected-note@below{{operand has semiring bool}} + a: bool) -> bool { + // expected-error@below{{negation is only supported for int and real types}} + return -a; +} +``` + ## Current Test Coverage -As of now, we have 136 parser tests covering: +As of now, we have 157 parser tests covering: - Duplicate definitions (functions and parameters) - Fill syntax errors (vector vs matrix, non-scalar expressions) - Masked assignment errors (dimension mismatches) @@ -323,3 +449,15 @@ As of now, we have 136 parser tests covering: - Variable scoping (loop-local variables) - For loop errors (non-integer start/end, non-dimension range, non-boolean until condition) - Return statement errors (in loop, not last, wrong type, multiple returns, missing return) +- Matrix multiplication dimension mismatches +- Built-in function validation (diag, apply, select) +- Element-wise operation type checking (semiring and dimension compatibility) +- Subtraction and negation semiring restrictions (only int and real) + +## Additional Tests to Add +- compare less than: fails on bool and trop_int semirings +- div: fails on int and trop_real semirings +- not: fails on int semiring +- literal: `bool(42)`, `int(42.0)` and `trop_real(false)` +- Use `TypeFormatter::format` when printing types in error messages in the parser. +- element-wise function application: look at the tests with have for `apply(..)` and element-wise addition, and add similar tests for element-wise function application (nr. of parameters, type mismatch, does the function to call exist) From b4ce5ad63d82573f3285f693d0749965740b4a20 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 25 Nov 2025 17:03:33 +0000 Subject: [PATCH 5/9] Even more errors. --- compiler/src/graphalg/parse/Parser.cpp | 102 +++++++++- .../parse-err/apply-func-non-scalar-args.gr | 2 +- .../test/parse-err/cmp-bool-unsupported.gr | 9 + .../parse-err/cmp-trop-int-unsupported.gr | 9 + .../test/parse-err/div-bool-unsupported.gr | 9 + .../test/parse-err/div-int-unsupported.gr | 9 + .../parse-err/div-trop-real-unsupported.gr | 9 + .../parse-err/ewise-func-non-scalar-params.gr | 11 ++ .../ewise-func-type-mismatch-left.gr | 13 ++ .../ewise-func-type-mismatch-right.gr | 14 ++ .../test/parse-err/ewise-func-undefined.gr | 6 + .../parse-err/ewise-func-wrong-param-count.gr | 11 ++ .../test/parse-err/literal-bool-from-int.gr | 6 + .../test/parse-err/literal-int-from-real.gr | 6 + .../parse-err/literal-trop-real-from-bool.gr | 6 + .../test/parse-err/not-int-unsupported.gr | 9 + .../test/parse-err/not-real-unsupported.gr | 9 + .../parse-err/not-trop-int-unsupported.gr | 9 + .../select-binary-func-non-bool-return.gr | 2 +- .../parse-err/select-func-non-bool-return.gr | 2 +- .../parse-err/select-func-non-scalar-args.gr | 2 +- compiler/test/parse/arith.gr | 2 +- docs/parse-tests.md | 181 +++++++++++++++++- 23 files changed, 420 insertions(+), 18 deletions(-) create mode 100644 compiler/test/parse-err/cmp-bool-unsupported.gr create mode 100644 compiler/test/parse-err/cmp-trop-int-unsupported.gr create mode 100644 compiler/test/parse-err/div-bool-unsupported.gr create mode 100644 compiler/test/parse-err/div-int-unsupported.gr create mode 100644 compiler/test/parse-err/div-trop-real-unsupported.gr create mode 100644 compiler/test/parse-err/ewise-func-non-scalar-params.gr create mode 100644 compiler/test/parse-err/ewise-func-type-mismatch-left.gr create mode 100644 compiler/test/parse-err/ewise-func-type-mismatch-right.gr create mode 100644 compiler/test/parse-err/ewise-func-undefined.gr create mode 100644 compiler/test/parse-err/ewise-func-wrong-param-count.gr create mode 100644 compiler/test/parse-err/literal-bool-from-int.gr create mode 100644 compiler/test/parse-err/literal-int-from-real.gr create mode 100644 compiler/test/parse-err/literal-trop-real-from-bool.gr create mode 100644 compiler/test/parse-err/not-int-unsupported.gr create mode 100644 compiler/test/parse-err/not-real-unsupported.gr create mode 100644 compiler/test/parse-err/not-trop-int-unsupported.gr diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index 91715d9..8b45d46 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -1146,6 +1146,59 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { return mlir::failure(); } + // Validate element-wise function application + auto funcType = funcOp.getFunctionType(); + + // Check that function takes exactly 2 parameters + if (funcType.getNumInputs() != 2) { + auto diag = mlir::emitError(loc) + << "element-wise function application requires a function " + "with 2 parameters, but got " + << funcType.getNumInputs(); + diag.attachNote(funcOp.getLoc()) << "function defined here"; + return mlir::failure(); + } + + // Check that all function parameters are scalars + for (size_t i = 0; i < 2; i++) { + auto paramType = llvm::dyn_cast(funcType.getInput(i)); + if (!paramType || !paramType.isScalar()) { + auto diag = mlir::emitError(loc) + << "element-wise function application requires function " + "parameters to be scalars"; + diag.attachNote(funcOp.getLoc()) + << "parameter " << i << " has type " + << typeToString(funcType.getInput(i)); + return mlir::failure(); + } + } + + // Check that operand types match function parameter types + auto lhsType = llvm::cast(atomLhs.getType()); + auto rhsType = llvm::cast(atomRhs.getType()); + auto param0Type = llvm::cast(funcType.getInput(0)); + auto param1Type = llvm::cast(funcType.getInput(1)); + + if (lhsType.getSemiring() != param0Type.getSemiring()) { + auto diag = mlir::emitError(loc) + << "left operand type does not match first parameter type"; + diag.attachNote(atomLhs.getLoc()) + << "left operand has type " << typeToString(lhsType.getSemiring()); + diag.attachNote(funcOp.getLoc()) + << "first parameter has type " << typeToString(param0Type.getSemiring()); + return mlir::failure(); + } + + if (rhsType.getSemiring() != param1Type.getSemiring()) { + auto diag = mlir::emitError(loc) + << "right operand type does not match second parameter type"; + diag.attachNote(atomRhs.getLoc()) + << "right operand has type " << typeToString(rhsType.getSemiring()); + diag.attachNote(funcOp.getLoc()) + << "second parameter has type " << typeToString(param1Type.getSemiring()); + return mlir::failure(); + } + atomLhs = _builder.create(loc, funcOp, atomLhs, atomRhs); } else { @@ -1317,6 +1370,37 @@ mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, } } + // Special checks for division + if (op == BinaryOp::DIV) { + // Division only supports real and trop_int semirings + auto *ctx = _builder.getContext(); + auto semiring = lhsType.getSemiring(); + if (semiring != SemiringTypes::forReal(ctx) && + semiring != SemiringTypes::forTropInt(ctx)) { + auto diag = mlir::emitError(loc) + << "division is only supported for real and trop_int types"; + diag.attachNote(lhs.getLoc()) + << "operands have semiring " << typeToString(semiring); + return nullptr; + } + } + + // Special checks for comparison operators + if (op == BinaryOp::LT || op == BinaryOp::GT || op == BinaryOp::LE || op == BinaryOp::GE) { + // Comparison only supports int, real, and trop_real semirings + auto *ctx = _builder.getContext(); + auto semiring = lhsType.getSemiring(); + if (semiring != SemiringTypes::forInt(ctx) && + semiring != SemiringTypes::forReal(ctx) && + semiring != SemiringTypes::forTropReal(ctx)) { + auto diag = mlir::emitError(loc) + << "comparison is only supported for int, real, and trop_real types"; + diag.attachNote(lhs.getLoc()) + << "operands have semiring " << typeToString(semiring); + return nullptr; + } + } + return _builder.create(loc, lhs, op, rhs); } @@ -1506,7 +1590,7 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { auto diag = mlir::emitError(loc) << "apply() requires function parameters to be scalars"; diag.attachNote(func.getLoc()) - << "parameter " << i << " has type " << funcType.getInput(i); + << "parameter " << i << " has type " << typeToString(funcType.getInput(i)); return mlir::failure(); } } @@ -1582,7 +1666,7 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { auto diag = mlir::emitError(loc) << "select() requires function parameters to be scalars"; diag.attachNote(func.getLoc()) - << "parameter " << i << " has type " << funcType.getInput(i); + << "parameter " << i << " has type " << typeToString(funcType.getInput(i)); return mlir::failure(); } } @@ -1602,7 +1686,7 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { auto diag = mlir::emitError(loc) << "select() requires function to return bool"; diag.attachNote(func.getLoc()) - << "function returns " << funcType.getResult(0); + << "function returns " << typeToString(funcType.getResult(0)); return mlir::failure(); } @@ -1729,6 +1813,18 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { return mlir::failure(); } + // Check that NOT is only used with bool semiring + auto vType = llvm::cast(v.getType()); + auto semiring = vType.getSemiring(); + auto *ctx = _builder.getContext(); + if (semiring != SemiringTypes::forBool(ctx)) { + auto diag = mlir::emitError(loc) + << "not operator is only supported for bool type"; + diag.attachNote(v.getLoc()) + << "operand has semiring " << typeToString(semiring); + return mlir::failure(); + } + v = _builder.create(loc, v); return mlir::success(); } diff --git a/compiler/test/parse-err/apply-func-non-scalar-args.gr b/compiler/test/parse-err/apply-func-non-scalar-args.gr index e2675c9..1d1c1f6 100644 --- a/compiler/test/parse-err/apply-func-non-scalar-args.gr +++ b/compiler/test/parse-err/apply-func-non-scalar-args.gr @@ -1,6 +1,6 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s -// expected-note@below{{parameter 0 has type '!graphalg.mat x distinct[0]<> x i64>'}} +// expected-note@below{{parameter 0 has type Matrix}} func nonScalarFunc(m: Matrix) -> int { return int(0); } diff --git a/compiler/test/parse-err/cmp-bool-unsupported.gr b/compiler/test/parse-err/cmp-bool-unsupported.gr new file mode 100644 index 0000000..aa583bc --- /dev/null +++ b/compiler/test/parse-err/cmp-bool-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func CmpBoolUnsupported( + // expected-note@below{{operands have semiring bool}} + a: bool, b: bool) -> bool { + // Comparison is not supported for bool semiring (only int, real, trop_real) + // expected-error@below{{comparison is only supported for int, real, and trop_real types}} + return a < b; +} diff --git a/compiler/test/parse-err/cmp-trop-int-unsupported.gr b/compiler/test/parse-err/cmp-trop-int-unsupported.gr new file mode 100644 index 0000000..729ae32 --- /dev/null +++ b/compiler/test/parse-err/cmp-trop-int-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func CmpTropIntUnsupported( + // expected-note@below{{operands have semiring trop_int}} + a: trop_int, b: trop_int) -> bool { + // Comparison is not supported for trop_int semiring (only int, real, trop_real) + // expected-error@below{{comparison is only supported for int, real, and trop_real types}} + return a < b; +} diff --git a/compiler/test/parse-err/div-bool-unsupported.gr b/compiler/test/parse-err/div-bool-unsupported.gr new file mode 100644 index 0000000..ca9db04 --- /dev/null +++ b/compiler/test/parse-err/div-bool-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func DivBoolUnsupported( + // expected-note@below{{operands have semiring bool}} + a: bool, b: bool) -> bool { + // Division is not supported for bool semiring (only real and trop_int) + // expected-error@below{{division is only supported for real and trop_int types}} + return a / b; +} diff --git a/compiler/test/parse-err/div-int-unsupported.gr b/compiler/test/parse-err/div-int-unsupported.gr new file mode 100644 index 0000000..3373a49 --- /dev/null +++ b/compiler/test/parse-err/div-int-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func DivIntUnsupported( + // expected-note@below{{operands have semiring int}} + a: int, b: int) -> int { + // Division is not supported for int semiring (only real and trop_int) + // expected-error@below{{division is only supported for real and trop_int types}} + return a / b; +} diff --git a/compiler/test/parse-err/div-trop-real-unsupported.gr b/compiler/test/parse-err/div-trop-real-unsupported.gr new file mode 100644 index 0000000..421530b --- /dev/null +++ b/compiler/test/parse-err/div-trop-real-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func DivTropRealUnsupported( + // expected-note@below{{operands have semiring trop_real}} + a: trop_real, b: trop_real) -> trop_real { + // Division is not supported for trop_real semiring (only real and trop_int) + // expected-error@below{{division is only supported for real and trop_int types}} + return a / b; +} diff --git a/compiler/test/parse-err/ewise-func-non-scalar-params.gr b/compiler/test/parse-err/ewise-func-non-scalar-params.gr new file mode 100644 index 0000000..6f1fed1 --- /dev/null +++ b/compiler/test/parse-err/ewise-func-non-scalar-params.gr @@ -0,0 +1,11 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{parameter 0 has type Matrix}} +func matrixParam(m: Matrix, n: int) -> int { + return n; +} + +func EwiseFuncNonScalarParams(m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{element-wise function application requires function parameters to be scalars}} + return m (.matrixParam) n; +} diff --git a/compiler/test/parse-err/ewise-func-type-mismatch-left.gr b/compiler/test/parse-err/ewise-func-type-mismatch-left.gr new file mode 100644 index 0000000..16d4118 --- /dev/null +++ b/compiler/test/parse-err/ewise-func-type-mismatch-left.gr @@ -0,0 +1,13 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{first parameter has type real}} +func addReals(a: real, b: real) -> real { + return a + b; +} + +func EwiseFuncTypeMismatchLeft( + // expected-note@below{{left operand has type int}} + m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{left operand type does not match first parameter type}} + return m (.addReals) n; +} diff --git a/compiler/test/parse-err/ewise-func-type-mismatch-right.gr b/compiler/test/parse-err/ewise-func-type-mismatch-right.gr new file mode 100644 index 0000000..517b2c6 --- /dev/null +++ b/compiler/test/parse-err/ewise-func-type-mismatch-right.gr @@ -0,0 +1,14 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{second parameter has type real}} +func addMixed(a: int, b: real) -> real { + return cast(a) + b; +} + +func EwiseFuncTypeMismatchRight( + m: Matrix, + // expected-note@below{{right operand has type int}} + n: Matrix) -> Matrix { + // expected-error@below{{right operand type does not match second parameter type}} + return m (.addMixed) n; +} diff --git a/compiler/test/parse-err/ewise-func-undefined.gr b/compiler/test/parse-err/ewise-func-undefined.gr new file mode 100644 index 0000000..9cbaa34 --- /dev/null +++ b/compiler/test/parse-err/ewise-func-undefined.gr @@ -0,0 +1,6 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func EwiseFuncUndefined(m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{unknown function 'doesNotExist'}} + return m (.doesNotExist) n; +} diff --git a/compiler/test/parse-err/ewise-func-wrong-param-count.gr b/compiler/test/parse-err/ewise-func-wrong-param-count.gr new file mode 100644 index 0000000..7abe4dc --- /dev/null +++ b/compiler/test/parse-err/ewise-func-wrong-param-count.gr @@ -0,0 +1,11 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +// expected-note@below{{function defined here}} +func oneParam(a: int) -> int { + return a; +} + +func EwiseFuncWrongParamCount(m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{element-wise function application requires a function with 2 parameters, but got 1}} + return m (.oneParam) n; +} diff --git a/compiler/test/parse-err/literal-bool-from-int.gr b/compiler/test/parse-err/literal-bool-from-int.gr new file mode 100644 index 0000000..7bbc15b --- /dev/null +++ b/compiler/test/parse-err/literal-bool-from-int.gr @@ -0,0 +1,6 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LiteralBoolFromInt() -> bool { + // expected-error@below{{expected 'true' or 'false'}} + return bool(42); +} diff --git a/compiler/test/parse-err/literal-int-from-real.gr b/compiler/test/parse-err/literal-int-from-real.gr new file mode 100644 index 0000000..1d50003 --- /dev/null +++ b/compiler/test/parse-err/literal-int-from-real.gr @@ -0,0 +1,6 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LiteralIntFromReal() -> int { + // expected-error@below{{expected an integer value}} + return int(42.0); +} diff --git a/compiler/test/parse-err/literal-trop-real-from-bool.gr b/compiler/test/parse-err/literal-trop-real-from-bool.gr new file mode 100644 index 0000000..7b9239a --- /dev/null +++ b/compiler/test/parse-err/literal-trop-real-from-bool.gr @@ -0,0 +1,6 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func LiteralTropRealFromBool() -> trop_real { + // expected-error@below{{expected a floating-point value}} + return trop_real(false); +} diff --git a/compiler/test/parse-err/not-int-unsupported.gr b/compiler/test/parse-err/not-int-unsupported.gr new file mode 100644 index 0000000..740ee47 --- /dev/null +++ b/compiler/test/parse-err/not-int-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func NotIntUnsupported( + // expected-note@below{{operand has semiring int}} + a: int) -> int { + // NOT is not supported for int semiring (only bool) + // expected-error@below{{not operator is only supported for bool type}} + return !a; +} diff --git a/compiler/test/parse-err/not-real-unsupported.gr b/compiler/test/parse-err/not-real-unsupported.gr new file mode 100644 index 0000000..c8256e2 --- /dev/null +++ b/compiler/test/parse-err/not-real-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func NotRealUnsupported( + // expected-note@below{{operand has semiring real}} + a: real) -> real { + // NOT is not supported for real semiring (only bool) + // expected-error@below{{not operator is only supported for bool type}} + return !a; +} diff --git a/compiler/test/parse-err/not-trop-int-unsupported.gr b/compiler/test/parse-err/not-trop-int-unsupported.gr new file mode 100644 index 0000000..30fe961 --- /dev/null +++ b/compiler/test/parse-err/not-trop-int-unsupported.gr @@ -0,0 +1,9 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func NotTropIntUnsupported( + // expected-note@below{{operand has semiring trop_int}} + a: trop_int) -> trop_int { + // NOT is not supported for trop_int semiring (only bool) + // expected-error@below{{not operator is only supported for bool type}} + return !a; +} diff --git a/compiler/test/parse-err/select-binary-func-non-bool-return.gr b/compiler/test/parse-err/select-binary-func-non-bool-return.gr index dc07b83..36b929f 100644 --- a/compiler/test/parse-err/select-binary-func-non-bool-return.gr +++ b/compiler/test/parse-err/select-binary-func-non-bool-return.gr @@ -1,6 +1,6 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s -// expected-note@below{{function returns '!graphalg.mat<1 x 1 x f64>'}} +// expected-note@below{{function returns real}} func returnsBinaryReal(a: int, b: int) -> real { return real(3.14); } diff --git a/compiler/test/parse-err/select-func-non-bool-return.gr b/compiler/test/parse-err/select-func-non-bool-return.gr index c5669a9..2d98147 100644 --- a/compiler/test/parse-err/select-func-non-bool-return.gr +++ b/compiler/test/parse-err/select-func-non-bool-return.gr @@ -1,6 +1,6 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s -// expected-note@below{{function returns '!graphalg.mat<1 x 1 x i64>'}} +// expected-note@below{{function returns int}} func returnsInt(a: int) -> int { return a; } diff --git a/compiler/test/parse-err/select-func-non-scalar-args.gr b/compiler/test/parse-err/select-func-non-scalar-args.gr index 08340ea..b18114e 100644 --- a/compiler/test/parse-err/select-func-non-scalar-args.gr +++ b/compiler/test/parse-err/select-func-non-scalar-args.gr @@ -1,6 +1,6 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s -// expected-note@below{{parameter 0 has type '!graphalg.mat x distinct[0]<> x i64>'}} +// expected-note@below{{parameter 0 has type Matrix}} func nonScalarFunc(m: Matrix) -> bool { return bool(true); } diff --git a/compiler/test/parse/arith.gr b/compiler/test/parse/arith.gr index e22fb30..cd5e8cd 100644 --- a/compiler/test/parse/arith.gr +++ b/compiler/test/parse/arith.gr @@ -22,7 +22,7 @@ func Mul(a:int, b:int, c:int) -> int { } // CHECK-LABEL: @Div -func Div(a:int, b:int, c:int) -> int { +func Div(a:real, b:real, c:real) -> real { // CHECK: %[[#DIV:]] = graphalg.ewise %arg0 DIV %arg1 // CHECK: return %[[#DIV]] return a / b; diff --git a/docs/parse-tests.md b/docs/parse-tests.md index 3ed0c8f..1370f2c 100644 --- a/docs/parse-tests.md +++ b/docs/parse-tests.md @@ -353,7 +353,7 @@ func ApplyUnaryFuncWrongArgCount(m: Matrix) -> Matrix { **apply() with non-scalar function parameters** (`apply-func-non-scalar-args.gr`): ```graphalg -// expected-note@below{{parameter 0 has type '!graphalg.mat x distinct[0]<> x i64>'}} +// expected-note@below{{parameter 0 has type Matrix}} func nonScalarFunc(m: Matrix) -> int { return int(0); } @@ -366,7 +366,7 @@ func ApplyFuncNonScalarArgs(m: Matrix) -> Matrix { **select() with non-bool return type** (`select-func-non-bool-return.gr`): ```graphalg -// expected-note@below{{function returns '!graphalg.mat<1 x 1 x i64>'}} +// expected-note@below{{function returns int}} func returnsInt(a: int) -> int { return a; } @@ -440,7 +440,7 @@ func NegBoolUnsupported( ## Current Test Coverage -As of now, we have 157 parser tests covering: +As of now, we have 173 parser tests covering: - Duplicate definitions (functions and parameters) - Fill syntax errors (vector vs matrix, non-scalar expressions) - Masked assignment errors (dimension mismatches) @@ -453,11 +453,172 @@ As of now, we have 157 parser tests covering: - Built-in function validation (diag, apply, select) - Element-wise operation type checking (semiring and dimension compatibility) - Subtraction and negation semiring restrictions (only int and real) +- **Comparison operator semiring restrictions (only int, real, and trop_real)** +- **Division semiring restrictions (only real and trop_int)** +- **NOT operator type restrictions (only bool)** +- **Literal type conversion validation** +- **Element-wise function application validation (parameter count, types, and existence)** +### 12. Comparison Operator Errors + +**Comparison with unsupported semiring** (`cmp-bool-unsupported.gr`, `cmp-trop-int-unsupported.gr`): +```graphalg +func CmpBoolUnsupported( + // expected-note@below{{operands have semiring bool}} + a: bool, b: bool) -> bool { + // expected-error@below{{comparison is only supported for int, real, and trop_real types}} + return a < b; +} +``` + +Comparison operators (`<`, `>`, `<=`, `>=`) only support int, real, and trop_real semirings. They are not supported for bool or trop_int. + +### 13. Division Errors + +**Division with unsupported semiring** (`div-int-unsupported.gr`, `div-trop-real-unsupported.gr`, `div-bool-unsupported.gr`): +```graphalg +func DivIntUnsupported( + // expected-note@below{{operands have semiring int}} + a: int, b: int) -> int { + // expected-error@below{{division is only supported for real and trop_int types}} + return a / b; +} +``` + +Division only supports real and trop_int semirings. It is not supported for int, bool, or trop_real. + +### 14. NOT Operator Errors + +**NOT with non-bool semiring** (`not-int-unsupported.gr`, `not-real-unsupported.gr`, `not-trop-int-unsupported.gr`): +```graphalg +func NotIntUnsupported( + // expected-note@below{{operand has semiring int}} + a: int) -> int { + // expected-error@below{{not operator is only supported for bool type}} + return !a; +} +``` + +The NOT operator (`!`) only works with bool type. It is not supported for any other semiring. + +### 15. Literal Type Conversion Errors + +**Invalid literal conversions** (`literal-bool-from-int.gr`, `literal-int-from-real.gr`, `literal-trop-real-from-bool.gr`): +```graphalg +func LiteralBoolFromInt() -> bool { + // expected-error@below{{expected 'true' or 'false'}} + return bool(42); +} + +func LiteralIntFromReal() -> int { + // expected-error@below{{expected an integer value}} + return int(42.0); +} + +func LiteralTropRealFromBool() -> trop_real { + // expected-error@below{{expected a floating-point value}} + return trop_real(false); +} +``` + +Each semiring type requires a specific literal format: +- `bool` requires `true` or `false` +- `int` and `trop_int` require integer literals +- `real` and `trop_real` require floating-point literals + +### 16. Element-wise Function Application Errors + +Element-wise function application uses the syntax `a (.func) b` to apply a binary function element-wise to two matrices. + +**Wrong parameter count** (`ewise-func-wrong-param-count.gr`): +```graphalg +// expected-note@below{{function defined here}} +func oneParam(a: int) -> int { + return a; +} + +func Test(m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{element-wise function application requires a function with 2 parameters, but got 1}} + return m (.oneParam) n; +} +``` + +**Non-scalar parameters** (`ewise-func-non-scalar-params.gr`): +```graphalg +// expected-note@below{{parameter 0 has type Matrix}} +func matrixParam(m: Matrix, n: int) -> int { + return n; +} + +func Test(m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{element-wise function application requires function parameters to be scalars}} + return m (.matrixParam) n; +} +``` + +**Type mismatch** (`ewise-func-type-mismatch-left.gr`, `ewise-func-type-mismatch-right.gr`): +```graphalg +// expected-note@below{{first parameter has type real}} +func addReals(a: real, b: real) -> real { + return a + b; +} + +func Test( + // expected-note@below{{left operand has type int}} + m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{left operand type does not match first parameter type}} + return m (.addReals) n; +} +``` + +**Undefined function** (`ewise-func-undefined.gr`): +```graphalg +func Test(m: Matrix, n: Matrix) -> Matrix { + // expected-error@below{{unknown function 'doesNotExist'}} + return m (.doesNotExist) n; +} +``` + +## Best Practices for Error Messages + +### Always Use TypeFormatter for Type Display + +When emitting error messages that include types, **always use `typeToString()`** instead of directly printing MLIR types. This ensures user-friendly error messages. + +**Correct:** +```cpp +auto diag = mlir::emitError(loc) + << "parameter has type " << typeToString(funcType.getInput(i)); +``` + +**Incorrect:** +```cpp +auto diag = mlir::emitError(loc) + << "parameter has type " << funcType.getInput(i); // Shows raw MLIR type +``` + +The `typeToString()` function uses `TypeFormatter` internally, which formats types in a user-friendly way: +- Raw MLIR: `!graphalg.mat x distinct[0]<> x i64>` +- Formatted: `Matrix` + +### Semiring Type Restrictions Summary + +| Operation | Supported Semirings | Notes | +|-----------|-------------------|-------| +| Addition (`+`) | All | No restrictions | +| Subtraction (`-`) | int, real | Scalar only without `(.-)` | +| Multiplication (`*`) | All | Matrix multiplication has dimension requirements | +| Division (`/`) | real, trop_int | | +| Comparison (`<`, `>`, `<=`, `>=`) | int, real, trop_real | | +| Equality (`==`, `!=`) | All | No restrictions | +| NOT (`!`) | bool | Only boolean values | +| Negation (`-x`) | int, real | Unary operator | + +### Validation Checklist for Element-wise Function Application + +When implementing validation for element-wise function application `a (.func) b`: -## Additional Tests to Add -- compare less than: fails on bool and trop_int semirings -- div: fails on int and trop_real semirings -- not: fails on int semiring -- literal: `bool(42)`, `int(42.0)` and `trop_real(false)` -- Use `TypeFormatter::format` when printing types in error messages in the parser. -- element-wise function application: look at the tests with have for `apply(..)` and element-wise addition, and add similar tests for element-wise function application (nr. of parameters, type mismatch, does the function to call exist) +1. ✅ Function must exist (checked by `parseFuncRef`) +2. ✅ Function must have exactly 2 parameters +3. ✅ All function parameters must be scalars +4. ✅ Left operand type must match first parameter type +5. ✅ Right operand type must match second parameter type From 642539b2c318dd491c396ec85d340e0b12864bd9 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 26 Nov 2025 09:30:30 +0000 Subject: [PATCH 6/9] Cleanup claude's work. --- .gitignore | 1 + compiler/src/graphalg/parse/Parser.cpp | 172 +++++++++--------- .../test/parse-err/cmp-bool-unsupported.gr | 2 +- .../parse-err/cmp-trop-int-unsupported.gr | 2 +- .../test/parse-err/div-bool-unsupported.gr | 2 +- .../test/parse-err/div-int-unsupported.gr | 2 +- .../parse-err/div-trop-real-unsupported.gr | 2 +- .../parse-err/ewise-different-dimensions.gr | 2 +- .../parse-err/ewise-different-semirings.gr | 2 +- .../test/parse-err/sub-bool-unsupported.gr | 2 +- .../test/parse-err/sub-matrix-not-scalar.gr | 6 +- .../parse-err/sub-trop-int-unsupported.gr | 2 +- 12 files changed, 103 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index c4ed460..d3af9fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /node_modules +/thirdparty # ===== BEGIN JEKYLL ===== diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index 8b45d46..dc98d55 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -89,9 +89,7 @@ class Parser { DimMapper _dimMapper; - // Track parsing context - int _loopDepth = 0; - bool _hasReturn = false; + // Expected return type for the current function. mlir::Type _expectedReturnType; Token cur() { return _tokens[_offset]; } @@ -181,7 +179,7 @@ class Parser { mlir::Value buildMatMul(mlir::Location loc, mlir::Value lhs, mlir::Value rhs); mlir::Value buildElementWise(mlir::Location loc, mlir::Value lhs, BinaryOp op, - mlir::Value rhs, bool isExplicitElementWise = false); + mlir::Value rhs, bool mustBeScalar); mlir::ParseResult parseAtom(mlir::Value &v); @@ -450,6 +448,11 @@ mlir::ParseResult Parser::parseProgram() { return mlir::success(); } +static bool hasReturn(mlir::Block &block) { + return block.mightHaveTerminator() && + llvm::isa(block.getTerminator()); +} + mlir::ParseResult Parser::parseFunction() { llvm::StringRef name; llvm::SmallVector paramNames; @@ -489,14 +492,13 @@ mlir::ParseResult Parser::parseFunction() { // Set expected return type for this function _expectedReturnType = returnType; - _hasReturn = false; if (mlir::failed(parseBlock())) { return mlir::failure(); } // Check for return statement - if (!_hasReturn) { + if (!hasReturn(entryBlock)) { return mlir::emitError(loc) << "function must have a return statement"; } @@ -567,7 +569,8 @@ mlir::ParseResult Parser::parseBlock() { } // Check if there are statements after a return - if (_hasReturn && cur().type != Token::RBRACE) { + if (hasReturn(*_builder.getInsertionBlock()) && + cur().type != Token::RBRACE) { return mlir::emitError(cur().loc) << "statement after return is not allowed"; } @@ -644,9 +647,6 @@ mlir::ParseResult Parser::parseStmtFor() { return mlir::failure(); } - // Increment loop depth - _loopDepth++; - // Find the variables modified inside the loop. llvm::SmallVector varNames; findModifiedBindingsInBlock(_tokens, _offset, varNames); @@ -775,9 +775,6 @@ mlir::ParseResult Parser::parseStmtFor() { } } - // Decrement loop depth - _loopDepth--; - return mlir::success(); } @@ -785,15 +782,16 @@ mlir::ParseResult Parser::parseStmtReturn() { auto loc = cur().loc; // Check if return is inside a loop - if (_loopDepth > 0) { + auto *parentOp = _builder.getInsertionBlock()->getParentOp(); + if (llvm::isa(parentOp)) { return mlir::emitError(loc) << "return statement inside a loop is not allowed"; } - // Check if we already have a return statement - if (_hasReturn) { - return mlir::emitError(loc) << "multiple return statements are not allowed"; - } + // The parser should not create any other nested ops apart from for loops, so + // we should be at the top-level of a function scope. + assert(llvm::isa(parentOp) && + "return outside of function body"); mlir::Value returnValue; if (eatOrError(Token::RETURN) || parseExpr(returnValue) || @@ -809,7 +807,6 @@ mlir::ParseResult Parser::parseStmtReturn() { << typeToString(returnValue.getType()); } - _hasReturn = true; _builder.create(loc, returnValue); return mlir::success(); } @@ -1151,10 +1148,11 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { // Check that function takes exactly 2 parameters if (funcType.getNumInputs() != 2) { - auto diag = mlir::emitError(loc) - << "element-wise function application requires a function " - "with 2 parameters, but got " - << funcType.getNumInputs(); + auto diag = + mlir::emitError(loc) + << "element-wise function application requires a function " + "with 2 parameters, but got " + << funcType.getNumInputs(); diag.attachNote(funcOp.getLoc()) << "function defined here"; return mlir::failure(); } @@ -1163,9 +1161,10 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { for (size_t i = 0; i < 2; i++) { auto paramType = llvm::dyn_cast(funcType.getInput(i)); if (!paramType || !paramType.isScalar()) { - auto diag = mlir::emitError(loc) - << "element-wise function application requires function " - "parameters to be scalars"; + auto diag = + mlir::emitError(loc) + << "element-wise function application requires function " + "parameters to be scalars"; diag.attachNote(funcOp.getLoc()) << "parameter " << i << " has type " << typeToString(funcType.getInput(i)); @@ -1180,22 +1179,28 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { auto param1Type = llvm::cast(funcType.getInput(1)); if (lhsType.getSemiring() != param0Type.getSemiring()) { - auto diag = mlir::emitError(loc) - << "left operand type does not match first parameter type"; + auto diag = + mlir::emitError(loc) + << "left operand type does not match first parameter type"; diag.attachNote(atomLhs.getLoc()) - << "left operand has type " << typeToString(lhsType.getSemiring()); + << "left operand has type " + << typeToString(lhsType.getSemiring()); diag.attachNote(funcOp.getLoc()) - << "first parameter has type " << typeToString(param0Type.getSemiring()); + << "first parameter has type " + << typeToString(param0Type.getSemiring()); return mlir::failure(); } if (rhsType.getSemiring() != param1Type.getSemiring()) { - auto diag = mlir::emitError(loc) - << "right operand type does not match second parameter type"; + auto diag = + mlir::emitError(loc) + << "right operand type does not match second parameter type"; diag.attachNote(atomRhs.getLoc()) - << "right operand has type " << typeToString(rhsType.getSemiring()); + << "right operand has type " + << typeToString(rhsType.getSemiring()); diag.attachNote(funcOp.getLoc()) - << "second parameter has type " << typeToString(param1Type.getSemiring()); + << "second parameter has type " + << typeToString(param1Type.getSemiring()); return mlir::failure(); } @@ -1211,7 +1216,8 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { return mlir::failure(); } - atomLhs = buildElementWise(loc, atomLhs, binop, atomRhs, true); + atomLhs = buildElementWise(loc, atomLhs, binop, atomRhs, + /*mustBeScalar=*/false); if (!atomLhs) { return mlir::failure(); } @@ -1232,7 +1238,8 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { return mlir::failure(); } } else { - atomLhs = buildElementWise(loc, atomLhs, binop, atomRhs); + atomLhs = buildElementWise(loc, atomLhs, binop, atomRhs, + /*mustBeScalar=*/true); if (!atomLhs) { return mlir::failure(); } @@ -1316,15 +1323,14 @@ mlir::Value Parser::buildMatMul(mlir::Location loc, mlir::Value lhs, } mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, - BinaryOp op, mlir::Value rhs, - bool isExplicitElementWise) { + BinaryOp op, mlir::Value rhs, + bool mustBeScalar) { auto lhsType = llvm::cast(lhs.getType()); auto rhsType = llvm::cast(rhs.getType()); // Check that semirings match if (lhsType.getSemiring() != rhsType.getSemiring()) { - auto diag = mlir::emitError(loc) - << "element-wise operation requires operands to have the same semiring"; + auto diag = mlir::emitError(loc) << "operands have different semirings"; diag.attachNote(lhs.getLoc()) << "left operand has semiring " << typeToString(lhsType.getSemiring()); diag.attachNote(rhs.getLoc()) @@ -1332,69 +1338,66 @@ mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, return nullptr; } - // Check that dimensions match - if (lhsType.getDims() != rhsType.getDims()) { - auto diag = mlir::emitError(loc) - << "element-wise operation requires operands to have the same dimensions"; - diag.attachNote(lhs.getLoc()) - << "left operand has dimensions " << dimsToString(lhsType.getDims()); - diag.attachNote(rhs.getLoc()) - << "right operand has dimensions " << dimsToString(rhsType.getDims()); - return nullptr; - } - - // Special checks for subtraction - if (op == BinaryOp::SUB) { - // Regular subtraction (e1 - e2) only works on scalars - // Element-wise subtraction (e1 (.-) e2) works on any dimensions - if (!isExplicitElementWise && (!lhsType.isScalar() || !rhsType.isScalar())) { + if (mustBeScalar) { + // Syntax a + b instead of a (.+) b, so require operands to be scalars. + if (!lhsType.isScalar() || !rhsType.isScalar()) { auto diag = mlir::emitError(loc) - << "subtraction only works on scalars; use element-wise subtraction (.-) for matrices"; + << "operands are not scalar. Did you mean to use " + "element-wise (a (.f) b) syntax?"; + diag.attachNote(lhs.getLoc()) + << "left operand has dimensions " << dimsToString(lhsType.getDims()); + diag.attachNote(rhs.getLoc()) + << "right operand has dimensions " << dimsToString(rhsType.getDims()); + return nullptr; + } + } else { + // Dimensions must match + if (lhsType.getDims() != rhsType.getDims()) { + auto diag = mlir::emitError(loc) << "operands have different dimensions"; diag.attachNote(lhs.getLoc()) - << "left operand has type " << typeToString(lhsType); + << "left operand has dimensions " << dimsToString(lhsType.getDims()); diag.attachNote(rhs.getLoc()) - << "right operand has type " << typeToString(rhsType); + << "right operand has dimensions " << dimsToString(rhsType.getDims()); return nullptr; } + } - // Subtraction (both forms) only supports int and real semirings + if (op == BinaryOp::SUB) { + // Subtraction only supports int and real semirings auto *ctx = _builder.getContext(); auto semiring = lhsType.getSemiring(); if (semiring != SemiringTypes::forInt(ctx) && semiring != SemiringTypes::forReal(ctx)) { auto diag = mlir::emitError(loc) - << "subtraction is only supported for int and real types"; + << "subtraction is only supported for int and real semirings"; diag.attachNote(lhs.getLoc()) << "operands have semiring " << typeToString(semiring); return nullptr; } } - // Special checks for division if (op == BinaryOp::DIV) { - // Division only supports real and trop_int semirings + // Division only supports real semiring auto *ctx = _builder.getContext(); auto semiring = lhsType.getSemiring(); - if (semiring != SemiringTypes::forReal(ctx) && - semiring != SemiringTypes::forTropInt(ctx)) { + if (semiring != SemiringTypes::forReal(ctx)) { auto diag = mlir::emitError(loc) - << "division is only supported for real and trop_int types"; + << "division is only supported for real semiring"; diag.attachNote(lhs.getLoc()) << "operands have semiring " << typeToString(semiring); return nullptr; } } - // Special checks for comparison operators - if (op == BinaryOp::LT || op == BinaryOp::GT || op == BinaryOp::LE || op == BinaryOp::GE) { - // Comparison only supports int, real, and trop_real semirings + if (op == BinaryOp::LT || op == BinaryOp::GT || op == BinaryOp::LE || + op == BinaryOp::GE) { + // Ordered compare only supports int, real semirings auto *ctx = _builder.getContext(); auto semiring = lhsType.getSemiring(); if (semiring != SemiringTypes::forInt(ctx) && - semiring != SemiringTypes::forReal(ctx) && - semiring != SemiringTypes::forTropReal(ctx)) { - auto diag = mlir::emitError(loc) - << "comparison is only supported for int, real, and trop_real types"; + semiring != SemiringTypes::forReal(ctx)) { + auto diag = mlir::emitError(loc) << "ordered compare is only supported " + "for int and real semirings"; diag.attachNote(lhs.getLoc()) << "operands have semiring " << typeToString(semiring); return nullptr; @@ -1557,7 +1560,8 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { // Unary apply: function must have exactly 1 argument if (numFuncArgs != 1) { auto diag = mlir::emitError(loc) - << "apply() with 1 matrix argument requires a function with 1 parameter, but got " + << "apply() with 1 matrix argument requires a function " + "with 1 parameter, but got " << numFuncArgs; diag.attachNote(func.getLoc()) << "function defined here"; return mlir::failure(); @@ -1566,7 +1570,8 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { // Binary apply: function must have exactly 2 arguments if (numFuncArgs != 2) { auto diag = mlir::emitError(loc) - << "apply() with 2 arguments requires a function with 2 parameters, but got " + << "apply() with 2 arguments requires a function with 2 " + "parameters, but got " << numFuncArgs; diag.attachNote(func.getLoc()) << "function defined here"; return mlir::failure(); @@ -1589,8 +1594,8 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { if (!paramType || !paramType.isScalar()) { auto diag = mlir::emitError(loc) << "apply() requires function parameters to be scalars"; - diag.attachNote(func.getLoc()) - << "parameter " << i << " has type " << typeToString(funcType.getInput(i)); + diag.attachNote(func.getLoc()) << "parameter " << i << " has type " + << typeToString(funcType.getInput(i)); return mlir::failure(); } } @@ -1633,7 +1638,8 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { // Unary select: function must have exactly 1 argument if (numFuncArgs != 1) { auto diag = mlir::emitError(loc) - << "select() with 1 matrix argument requires a function with 1 parameter, but got " + << "select() with 1 matrix argument requires a function " + "with 1 parameter, but got " << numFuncArgs; diag.attachNote(func.getLoc()) << "function defined here"; return mlir::failure(); @@ -1642,7 +1648,8 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { // Binary select: function must have exactly 2 arguments if (numFuncArgs != 2) { auto diag = mlir::emitError(loc) - << "select() with 2 arguments requires a function with 2 parameters, but got " + << "select() with 2 arguments requires a function with 2 " + "parameters, but got " << numFuncArgs; diag.attachNote(func.getLoc()) << "function defined here"; return mlir::failure(); @@ -1665,8 +1672,8 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { if (!paramType || !paramType.isScalar()) { auto diag = mlir::emitError(loc) << "select() requires function parameters to be scalars"; - diag.attachNote(func.getLoc()) - << "parameter " << i << " has type " << typeToString(funcType.getInput(i)); + diag.attachNote(func.getLoc()) << "parameter " << i << " has type " + << typeToString(funcType.getInput(i)); return mlir::failure(); } } @@ -1681,7 +1688,8 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { auto returnType = llvm::dyn_cast(funcType.getResult(0)); auto *ctx = _builder.getContext(); - auto expectedReturnType = MatrixType::scalarOf(SemiringTypes::forBool(ctx)); + auto expectedReturnType = + MatrixType::scalarOf(SemiringTypes::forBool(ctx)); if (!returnType || returnType != expectedReturnType) { auto diag = mlir::emitError(loc) << "select() requires function to return bool"; diff --git a/compiler/test/parse-err/cmp-bool-unsupported.gr b/compiler/test/parse-err/cmp-bool-unsupported.gr index aa583bc..00a2cf2 100644 --- a/compiler/test/parse-err/cmp-bool-unsupported.gr +++ b/compiler/test/parse-err/cmp-bool-unsupported.gr @@ -4,6 +4,6 @@ func CmpBoolUnsupported( // expected-note@below{{operands have semiring bool}} a: bool, b: bool) -> bool { // Comparison is not supported for bool semiring (only int, real, trop_real) - // expected-error@below{{comparison is only supported for int, real, and trop_real types}} + // expected-error@below{{ordered compare is only supported for int and real semirings}} return a < b; } diff --git a/compiler/test/parse-err/cmp-trop-int-unsupported.gr b/compiler/test/parse-err/cmp-trop-int-unsupported.gr index 729ae32..9bbc9ff 100644 --- a/compiler/test/parse-err/cmp-trop-int-unsupported.gr +++ b/compiler/test/parse-err/cmp-trop-int-unsupported.gr @@ -4,6 +4,6 @@ func CmpTropIntUnsupported( // expected-note@below{{operands have semiring trop_int}} a: trop_int, b: trop_int) -> bool { // Comparison is not supported for trop_int semiring (only int, real, trop_real) - // expected-error@below{{comparison is only supported for int, real, and trop_real types}} + // expected-error@below{{ordered compare is only supported for int and real semirings}} return a < b; } diff --git a/compiler/test/parse-err/div-bool-unsupported.gr b/compiler/test/parse-err/div-bool-unsupported.gr index ca9db04..5e60aa6 100644 --- a/compiler/test/parse-err/div-bool-unsupported.gr +++ b/compiler/test/parse-err/div-bool-unsupported.gr @@ -4,6 +4,6 @@ func DivBoolUnsupported( // expected-note@below{{operands have semiring bool}} a: bool, b: bool) -> bool { // Division is not supported for bool semiring (only real and trop_int) - // expected-error@below{{division is only supported for real and trop_int types}} + // expected-error@below{{division is only supported for real semiring}} return a / b; } diff --git a/compiler/test/parse-err/div-int-unsupported.gr b/compiler/test/parse-err/div-int-unsupported.gr index 3373a49..277c8a4 100644 --- a/compiler/test/parse-err/div-int-unsupported.gr +++ b/compiler/test/parse-err/div-int-unsupported.gr @@ -4,6 +4,6 @@ func DivIntUnsupported( // expected-note@below{{operands have semiring int}} a: int, b: int) -> int { // Division is not supported for int semiring (only real and trop_int) - // expected-error@below{{division is only supported for real and trop_int types}} + // expected-error@below{{division is only supported for real semiring}} return a / b; } diff --git a/compiler/test/parse-err/div-trop-real-unsupported.gr b/compiler/test/parse-err/div-trop-real-unsupported.gr index 421530b..4673f1c 100644 --- a/compiler/test/parse-err/div-trop-real-unsupported.gr +++ b/compiler/test/parse-err/div-trop-real-unsupported.gr @@ -4,6 +4,6 @@ func DivTropRealUnsupported( // expected-note@below{{operands have semiring trop_real}} a: trop_real, b: trop_real) -> trop_real { // Division is not supported for trop_real semiring (only real and trop_int) - // expected-error@below{{division is only supported for real and trop_int types}} + // expected-error@below{{division is only supported for real semiring}} return a / b; } diff --git a/compiler/test/parse-err/ewise-different-dimensions.gr b/compiler/test/parse-err/ewise-different-dimensions.gr index 1183146..061cb21 100644 --- a/compiler/test/parse-err/ewise-different-dimensions.gr +++ b/compiler/test/parse-err/ewise-different-dimensions.gr @@ -6,6 +6,6 @@ func EwiseDifferentDimensions( // expected-note@below{{right operand has dimensions (t x t)}} b: Matrix) -> Matrix { // Element-wise operations require operands to have the same dimensions - // expected-error@below{{element-wise operation requires operands to have the same dimensions}} + // expected-error@below{{operands have different dimensions}} return a (.+) b; } diff --git a/compiler/test/parse-err/ewise-different-semirings.gr b/compiler/test/parse-err/ewise-different-semirings.gr index 6e36446..3e4a31c 100644 --- a/compiler/test/parse-err/ewise-different-semirings.gr +++ b/compiler/test/parse-err/ewise-different-semirings.gr @@ -6,6 +6,6 @@ func EwiseDifferentSemirings( // expected-note@below{{right operand has semiring real}} b: Matrix) -> Matrix { // Element-wise operations require operands to have the same semiring - // expected-error@below{{element-wise operation requires operands to have the same semiring}} + // expected-error@below{{operands have different semirings}} return a (.+) b; } diff --git a/compiler/test/parse-err/sub-bool-unsupported.gr b/compiler/test/parse-err/sub-bool-unsupported.gr index f0d9d83..109e520 100644 --- a/compiler/test/parse-err/sub-bool-unsupported.gr +++ b/compiler/test/parse-err/sub-bool-unsupported.gr @@ -4,6 +4,6 @@ func SubBoolUnsupported( // expected-note@below{{operands have semiring bool}} a: bool, b: bool) -> bool { // Subtraction is not supported for bool semiring (only int and real) - // expected-error@below{{subtraction is only supported for int and real types}} + // expected-error@below{{subtraction is only supported for int and real semirings}} return a - b; } diff --git a/compiler/test/parse-err/sub-matrix-not-scalar.gr b/compiler/test/parse-err/sub-matrix-not-scalar.gr index 0e1f46c..b4415bb 100644 --- a/compiler/test/parse-err/sub-matrix-not-scalar.gr +++ b/compiler/test/parse-err/sub-matrix-not-scalar.gr @@ -1,11 +1,11 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s func SubMatrixNotScalar( - // expected-note@below{{left operand has type Matrix}} + // expected-note@below{{left operand has dimensions (s x s)}} a: Matrix, - // expected-note@below{{right operand has type Matrix}} + // expected-note@below{{right operand has dimensions (s x s)}} b: Matrix) -> Matrix { // Subtraction only works on scalars, not matrices - // expected-error@below{{subtraction only works on scalars; use element-wise subtraction (.-) for matrices}} + // expected-error@below{{operands are not scalar. Did you mean to use element-wise (a (.f) b) syntax?}} return a - b; } diff --git a/compiler/test/parse-err/sub-trop-int-unsupported.gr b/compiler/test/parse-err/sub-trop-int-unsupported.gr index b8f0ce8..192a9e1 100644 --- a/compiler/test/parse-err/sub-trop-int-unsupported.gr +++ b/compiler/test/parse-err/sub-trop-int-unsupported.gr @@ -4,6 +4,6 @@ func SubTropIntUnsupported( // expected-note@below{{operands have semiring trop_int}} a: trop_int, b: trop_int) -> trop_int { // Subtraction is not supported for tropical semirings (only int and real) - // expected-error@below{{subtraction is only supported for int and real types}} + // expected-error@below{{subtraction is only supported for int and real semirings}} return a - b; } From 1b4742020378f9fa2a9e2cd0ec295a92852b34a4 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 26 Nov 2025 09:46:01 +0000 Subject: [PATCH 7/9] Break down parseExpr more. --- compiler/src/graphalg/parse/Parser.cpp | 166 ++++++++++++++----------- 1 file changed, 91 insertions(+), 75 deletions(-) diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index dc98d55..a929bf9 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -181,6 +181,12 @@ class Parser { mlir::Value buildElementWise(mlir::Location loc, mlir::Value lhs, BinaryOp op, mlir::Value rhs, bool mustBeScalar); + mlir::Value buildElementWiseFunc(mlir::Location loc, mlir::Value lhs, + mlir::func::FuncOp funcOp, mlir::Value rhs); + + mlir::Value buildDotProperty(mlir::Location loc, mlir::Value value, + llvm::StringRef property); + mlir::ParseResult parseAtom(mlir::Value &v); mlir::ParseResult parseLiteral(mlir::Type ring, mlir::Value &v); @@ -1115,21 +1121,14 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { << "expected matrix property such as 'nrows'"; } - if (cur().body == "T") { - atomLhs = _builder.create(cur().loc, atomLhs); - } else if (cur().body == "nrows") { - auto matType = llvm::cast(atomLhs.getType()); - atomLhs = _builder.create(cur().loc, matType.getRows()); - } else if (cur().body == "ncols") { - auto matType = llvm::cast(atomLhs.getType()); - atomLhs = _builder.create(cur().loc, matType.getCols()); - } else if (cur().body == "nvals") { - atomLhs = _builder.create(cur().loc, atomLhs); - } else { - return mlir::emitError(cur().loc) << "invalid matrix property"; - } - + auto loc = cur().loc; + auto property = cur().body; eat(); + + atomLhs = buildDotProperty(loc, atomLhs, property); + if (!atomLhs) { + return mlir::failure(); + } } else if (ewise) { eat(); // '(' eat(); // '.' @@ -1143,69 +1142,10 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { return mlir::failure(); } - // Validate element-wise function application - auto funcType = funcOp.getFunctionType(); - - // Check that function takes exactly 2 parameters - if (funcType.getNumInputs() != 2) { - auto diag = - mlir::emitError(loc) - << "element-wise function application requires a function " - "with 2 parameters, but got " - << funcType.getNumInputs(); - diag.attachNote(funcOp.getLoc()) << "function defined here"; - return mlir::failure(); - } - - // Check that all function parameters are scalars - for (size_t i = 0; i < 2; i++) { - auto paramType = llvm::dyn_cast(funcType.getInput(i)); - if (!paramType || !paramType.isScalar()) { - auto diag = - mlir::emitError(loc) - << "element-wise function application requires function " - "parameters to be scalars"; - diag.attachNote(funcOp.getLoc()) - << "parameter " << i << " has type " - << typeToString(funcType.getInput(i)); - return mlir::failure(); - } - } - - // Check that operand types match function parameter types - auto lhsType = llvm::cast(atomLhs.getType()); - auto rhsType = llvm::cast(atomRhs.getType()); - auto param0Type = llvm::cast(funcType.getInput(0)); - auto param1Type = llvm::cast(funcType.getInput(1)); - - if (lhsType.getSemiring() != param0Type.getSemiring()) { - auto diag = - mlir::emitError(loc) - << "left operand type does not match first parameter type"; - diag.attachNote(atomLhs.getLoc()) - << "left operand has type " - << typeToString(lhsType.getSemiring()); - diag.attachNote(funcOp.getLoc()) - << "first parameter has type " - << typeToString(param0Type.getSemiring()); - return mlir::failure(); - } - - if (rhsType.getSemiring() != param1Type.getSemiring()) { - auto diag = - mlir::emitError(loc) - << "right operand type does not match second parameter type"; - diag.attachNote(atomRhs.getLoc()) - << "right operand has type " - << typeToString(rhsType.getSemiring()); - diag.attachNote(funcOp.getLoc()) - << "second parameter has type " - << typeToString(param1Type.getSemiring()); + atomLhs = buildElementWiseFunc(loc, atomLhs, funcOp, atomRhs); + if (!atomLhs) { return mlir::failure(); } - - atomLhs = - _builder.create(loc, funcOp, atomLhs, atomRhs); } else { // element-wise binop auto loc = cur().loc; @@ -1407,6 +1347,82 @@ mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, return _builder.create(loc, lhs, op, rhs); } +mlir::Value Parser::buildElementWiseFunc(mlir::Location loc, mlir::Value lhs, + mlir::func::FuncOp funcOp, + mlir::Value rhs) { + // Validate element-wise function application + auto funcType = funcOp.getFunctionType(); + + // Check that function takes exactly 2 parameters + if (funcType.getNumInputs() != 2) { + auto diag = mlir::emitError(loc) + << "element-wise function application requires a function " + "with 2 parameters, but got " + << funcType.getNumInputs(); + diag.attachNote(funcOp.getLoc()) << "function defined here"; + return nullptr; + } + + // Check that all function parameters are scalars + for (auto [i, t] : llvm::enumerate(funcType.getInputs())) { + auto paramType = llvm::cast(t); + if (!paramType.isScalar()) { + auto diag = mlir::emitError(loc) + << "element-wise function application requires function " + "parameters to be scalars"; + diag.attachNote(funcOp.getLoc()) + << "parameter " << i << " has type " << typeToString(paramType); + return nullptr; + } + } + + // Check that operand types match function parameter types + auto lhsType = llvm::cast(lhs.getType()); + auto rhsType = llvm::cast(rhs.getType()); + auto param0Type = llvm::cast(funcType.getInput(0)); + auto param1Type = llvm::cast(funcType.getInput(1)); + + if (lhsType.getSemiring() != param0Type.getSemiring()) { + auto diag = mlir::emitError(loc) + << "left operand type does not match first parameter type"; + diag.attachNote(lhs.getLoc()) + << "left operand has type " << typeToString(lhsType.getSemiring()); + diag.attachNote(funcOp.getLoc()) << "first parameter has type " + << typeToString(param0Type.getSemiring()); + return nullptr; + } + + if (rhsType.getSemiring() != param1Type.getSemiring()) { + auto diag = mlir::emitError(loc) + << "right operand type does not match second parameter type"; + diag.attachNote(rhs.getLoc()) + << "right operand has type " << typeToString(rhsType.getSemiring()); + diag.attachNote(funcOp.getLoc()) << "second parameter has type " + << typeToString(param1Type.getSemiring()); + return nullptr; + } + + return _builder.create(loc, funcOp, lhs, rhs); +} + +mlir::Value Parser::buildDotProperty(mlir::Location loc, mlir::Value value, + llvm::StringRef property) { + if (property == "T") { + return _builder.create(loc, value); + } else if (property == "nrows") { + auto matType = llvm::cast(value.getType()); + return _builder.create(loc, matType.getRows()); + } else if (property == "ncols") { + auto matType = llvm::cast(value.getType()); + return _builder.create(loc, matType.getCols()); + } else if (property == "nvals") { + return _builder.create(loc, value); + } else { + mlir::emitError(loc) << "invalid matrix property"; + return nullptr; + } +} + mlir::ParseResult Parser::parseAtom(mlir::Value &v) { auto loc = cur().loc; switch (cur().type) { From 53202a28c0110f5045637be1936de238682dc4b8 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 26 Nov 2025 10:21:05 +0000 Subject: [PATCH 8/9] cleaning up more. --- compiler/src/graphalg/parse/Parser.cpp | 788 ++++++++++-------- .../parse-err/ewise-func-non-scalar-params.gr | 3 +- .../ewise-func-type-mismatch-left.gr | 6 +- .../ewise-func-type-mismatch-right.gr | 6 +- 4 files changed, 440 insertions(+), 363 deletions(-) diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index a929bf9..000d1c6 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -34,6 +34,7 @@ #include "graphalg/SemiringTypes.h" #include "graphalg/parse/Lexer.h" #include "graphalg/parse/Parser.h" +#include "llvm/ADT/StringMap.h" namespace graphalg { @@ -189,6 +190,21 @@ class Parser { mlir::ParseResult parseAtom(mlir::Value &v); + mlir::ParseResult parseAtomMatrix(mlir::Value &v); + mlir::ParseResult parseAtomVector(mlir::Value &v); + mlir::ParseResult parseAtomCast(mlir::Value &v); + mlir::ParseResult parseAtomZero(mlir::Value &v); + mlir::ParseResult parseAtomOne(mlir::Value &v); + mlir::ParseResult parseAtomApply(mlir::Value &v); + mlir::ParseResult parseAtomSelect(mlir::Value &v); + mlir::ParseResult parseAtomReduceRows(mlir::Value &v); + mlir::ParseResult parseAtomReduceCols(mlir::Value &v); + mlir::ParseResult parseAtomReduce(mlir::Value &v); + mlir::ParseResult parseAtomPickAny(mlir::Value &v); + mlir::ParseResult parseAtomDiag(mlir::Value &v); + mlir::ParseResult parseAtomTril(mlir::Value &v); + mlir::ParseResult parseAtomTriu(mlir::Value &v); + mlir::ParseResult parseLiteral(mlir::Type ring, mlir::Value &v); public: @@ -1302,6 +1318,7 @@ mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, } } + // Additional validation for specific operators if (op == BinaryOp::SUB) { // Subtraction only supports int and real semirings auto *ctx = _builder.getContext(); @@ -1314,9 +1331,7 @@ mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, << "operands have semiring " << typeToString(semiring); return nullptr; } - } - - if (op == BinaryOp::DIV) { + } else if (op == BinaryOp::DIV) { // Division only supports real semiring auto *ctx = _builder.getContext(); auto semiring = lhsType.getSemiring(); @@ -1327,10 +1342,8 @@ mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, << "operands have semiring " << typeToString(semiring); return nullptr; } - } - - if (op == BinaryOp::LT || op == BinaryOp::GT || op == BinaryOp::LE || - op == BinaryOp::GE) { + } else if (op == BinaryOp::LT || op == BinaryOp::GT || op == BinaryOp::LE || + op == BinaryOp::GE) { // Ordered compare only supports int, real semirings auto *ctx = _builder.getContext(); auto semiring = lhsType.getSemiring(); @@ -1363,41 +1376,41 @@ mlir::Value Parser::buildElementWiseFunc(mlir::Location loc, mlir::Value lhs, return nullptr; } - // Check that all function parameters are scalars - for (auto [i, t] : llvm::enumerate(funcType.getInputs())) { - auto paramType = llvm::cast(t); - if (!paramType.isScalar()) { - auto diag = mlir::emitError(loc) - << "element-wise function application requires function " - "parameters to be scalars"; - diag.attachNote(funcOp.getLoc()) - << "parameter " << i << " has type " << typeToString(paramType); - return nullptr; - } - } - - // Check that operand types match function parameter types auto lhsType = llvm::cast(lhs.getType()); auto rhsType = llvm::cast(rhs.getType()); auto param0Type = llvm::cast(funcType.getInput(0)); auto param1Type = llvm::cast(funcType.getInput(1)); + // Check that function parameters are scalars. + if (!param0Type.isScalar() || !param1Type.isScalar()) { + auto diag = mlir::emitError(loc) + << "element-wise function application requires function " + "parameters to be scalars"; + diag.attachNote(funcOp.getLoc()) + << "first parameter has type " << typeToString(param0Type); + diag.attachNote(funcOp.getLoc()) + << "second parameter has type " << typeToString(param1Type); + return nullptr; + } + + // Check that operand semirings match function parameter semirings if (lhsType.getSemiring() != param0Type.getSemiring()) { auto diag = mlir::emitError(loc) - << "left operand type does not match first parameter type"; + << "left operand semiring does not match first parameter type"; diag.attachNote(lhs.getLoc()) - << "left operand has type " << typeToString(lhsType.getSemiring()); - diag.attachNote(funcOp.getLoc()) << "first parameter has type " + << "left operand has semiring " << typeToString(lhsType.getSemiring()); + diag.attachNote(funcOp.getLoc()) << "first parameter has semiring " << typeToString(param0Type.getSemiring()); return nullptr; } if (rhsType.getSemiring() != param1Type.getSemiring()) { - auto diag = mlir::emitError(loc) - << "right operand type does not match second parameter type"; + auto diag = + mlir::emitError(loc) + << "right operand semiring does not match second parameter type"; diag.attachNote(rhs.getLoc()) - << "right operand has type " << typeToString(rhsType.getSemiring()); - diag.attachNote(funcOp.getLoc()) << "second parameter has type " + << "right operand has semiring " << typeToString(rhsType.getSemiring()); + diag.attachNote(funcOp.getLoc()) << "second parameter has semiring " << typeToString(param1Type.getSemiring()); return nullptr; } @@ -1453,375 +1466,61 @@ mlir::ParseResult Parser::parseAtom(mlir::Value &v) { } if (name == "Matrix") { - mlir::Type ring; - mlir::Value rowsExpr; - mlir::Value colsExpr; - if (eatOrError(Token::LANGLE) || parseSemiring(ring) || - eatOrError(Token::RANGLE) || eatOrError(Token::LPAREN) || - parseExpr(rowsExpr) || eatOrError(Token::COMMA) || - parseExpr(colsExpr) || eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - // TODO: Better ref locs - auto rows = inferDim(rowsExpr, loc); - auto cols = inferDim(colsExpr, loc); - if (!rows || !cols) { - return mlir::failure(); - } - - v = _builder.create( - loc, _builder.getType(rows, cols, ring), - llvm::cast(ring).addIdentity()); - return mlir::success(); + return parseAtomMatrix(v); } if (name == "Vector") { - mlir::Type ring; - mlir::Value rowsExpr; - if (eatOrError(Token::LANGLE) || parseSemiring(ring) || - eatOrError(Token::RANGLE) || eatOrError(Token::LPAREN) || - parseExpr(rowsExpr) || eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - // TODO: Better ref locs - auto rows = inferDim(rowsExpr, loc); - if (!rows) { - return mlir::failure(); - } - - auto *ctx = _builder.getContext(); - v = _builder.create( - loc, MatrixType::get(ctx, rows, DimAttr::getOne(ctx), ring), - llvm::cast(ring).addIdentity()); - return mlir::success(); + return parseAtomVector(v); } if (name == "cast") { - mlir::Type ring; - mlir::Value expr; - if (eatOrError(Token::LANGLE) || parseSemiring(ring) || - eatOrError(Token::RANGLE) || eatOrError(Token::LPAREN) || - parseExpr(expr) || eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - auto exprType = llvm::cast(expr.getType()); - auto *dialect = - _builder.getContext()->getLoadedDialect(); - if (!dialect->isCastLegal(exprType.getSemiring(), ring)) { - return mlir::emitError(loc) - << "invalid cast from " << typeToString(exprType.getSemiring()) - << " to " << typeToString(ring); - } - - v = _builder.create( - loc, - _builder.getType(exprType.getRows(), exprType.getCols(), - ring), - expr); - return mlir::success(); + return parseAtomCast(v); } if (name == "zero") { - mlir::Type ring; - if (eatOrError(Token::LPAREN) || parseSemiring(ring) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - auto value = llvm::cast(ring).addIdentity(); - v = _builder.create(loc, value); - return mlir::success(); + return parseAtomZero(v); } if (name == "one") { - mlir::Type ring; - if (eatOrError(Token::LPAREN) || parseSemiring(ring) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - auto value = llvm::cast(ring).mulIdentity(); - v = _builder.create(loc, value); - return mlir::success(); + return parseAtomOne(v); } if (name == "apply") { - mlir::func::FuncOp func; - llvm::SmallVector args(1); - if (eatOrError(Token::LPAREN) || parseFuncRef(func) || - eatOrError(Token::COMMA) || parseExpr(args[0])) { - return mlir::failure(); - } - - if (cur().type == Token::COMMA) { - // Have a second arg. - auto &arg = args.emplace_back(); - if (eatOrError(Token::COMMA) || parseExpr(arg)) { - return mlir::failure(); - } - } - - if (eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - // Validate function signature - auto funcType = func.getFunctionType(); - size_t numFuncArgs = funcType.getNumInputs(); - - if (args.size() == 1) { - // Unary apply: function must have exactly 1 argument - if (numFuncArgs != 1) { - auto diag = mlir::emitError(loc) - << "apply() with 1 matrix argument requires a function " - "with 1 parameter, but got " - << numFuncArgs; - diag.attachNote(func.getLoc()) << "function defined here"; - return mlir::failure(); - } - } else { - // Binary apply: function must have exactly 2 arguments - if (numFuncArgs != 2) { - auto diag = mlir::emitError(loc) - << "apply() with 2 arguments requires a function with 2 " - "parameters, but got " - << numFuncArgs; - diag.attachNote(func.getLoc()) << "function defined here"; - return mlir::failure(); - } - - // Second argument must be a scalar - auto arg1Type = llvm::cast(args[1].getType()); - if (!arg1Type.isScalar()) { - auto diag = mlir::emitError(loc) - << "second argument to apply() must be a scalar"; - diag.attachNote(args[1].getLoc()) - << "argument has type " << typeToString(arg1Type); - return mlir::failure(); - } - } - - // Check that all function parameters are scalars - for (size_t i = 0; i < numFuncArgs; i++) { - auto paramType = llvm::dyn_cast(funcType.getInput(i)); - if (!paramType || !paramType.isScalar()) { - auto diag = mlir::emitError(loc) - << "apply() requires function parameters to be scalars"; - diag.attachNote(func.getLoc()) << "parameter " << i << " has type " - << typeToString(funcType.getInput(i)); - return mlir::failure(); - } - } - - if (args.size() == 1) { - v = _builder.create(loc, func, args[0]); - } else { - assert(args.size() == 2); - v = _builder.create(loc, func, args[0], args[1]); - } - - return mlir::success(); + return parseAtomApply(v); } if (name == "select") { - mlir::func::FuncOp func; - llvm::SmallVector args(1); - if (eatOrError(Token::LPAREN) || parseFuncRef(func) || - eatOrError(Token::COMMA) || parseExpr(args[0])) { - return mlir::failure(); - } - - if (cur().type == Token::COMMA) { - // Have a second arg. - auto &arg = args.emplace_back(); - if (eatOrError(Token::COMMA) || parseExpr(arg)) { - return mlir::failure(); - } - } - - if (eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - // Validate function signature - auto funcType = func.getFunctionType(); - size_t numFuncArgs = funcType.getNumInputs(); - - if (args.size() == 1) { - // Unary select: function must have exactly 1 argument - if (numFuncArgs != 1) { - auto diag = mlir::emitError(loc) - << "select() with 1 matrix argument requires a function " - "with 1 parameter, but got " - << numFuncArgs; - diag.attachNote(func.getLoc()) << "function defined here"; - return mlir::failure(); - } - } else { - // Binary select: function must have exactly 2 arguments - if (numFuncArgs != 2) { - auto diag = mlir::emitError(loc) - << "select() with 2 arguments requires a function with 2 " - "parameters, but got " - << numFuncArgs; - diag.attachNote(func.getLoc()) << "function defined here"; - return mlir::failure(); - } - - // Second argument must be a scalar - auto arg1Type = llvm::cast(args[1].getType()); - if (!arg1Type.isScalar()) { - auto diag = mlir::emitError(loc) - << "second argument to select() must be a scalar"; - diag.attachNote(args[1].getLoc()) - << "argument has type " << typeToString(arg1Type); - return mlir::failure(); - } - } - - // Check that all function parameters are scalars - for (size_t i = 0; i < numFuncArgs; i++) { - auto paramType = llvm::dyn_cast(funcType.getInput(i)); - if (!paramType || !paramType.isScalar()) { - auto diag = mlir::emitError(loc) - << "select() requires function parameters to be scalars"; - diag.attachNote(func.getLoc()) << "parameter " << i << " has type " - << typeToString(funcType.getInput(i)); - return mlir::failure(); - } - } - - // Check that function returns a boolean scalar - if (funcType.getNumResults() != 1) { - auto diag = mlir::emitError(loc) - << "select() requires function to return exactly one value"; - diag.attachNote(func.getLoc()) << "function defined here"; - return mlir::failure(); - } - - auto returnType = llvm::dyn_cast(funcType.getResult(0)); - auto *ctx = _builder.getContext(); - auto expectedReturnType = - MatrixType::scalarOf(SemiringTypes::forBool(ctx)); - if (!returnType || returnType != expectedReturnType) { - auto diag = mlir::emitError(loc) - << "select() requires function to return bool"; - diag.attachNote(func.getLoc()) - << "function returns " << typeToString(funcType.getResult(0)); - return mlir::failure(); - } - - if (args.size() == 1) { - v = _builder.create(loc, func.getSymName(), args[0]); - } else { - assert(args.size() == 2); - v = _builder.create(loc, func.getSymName(), args[0], - args[1]); - } - - return mlir::success(); + return parseAtomSelect(v); } if (name == "reduceRows") { - mlir::Value arg; - if (eatOrError(Token::LPAREN) || parseExpr(arg) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - auto inputType = llvm::cast(arg.getType()); - auto *ctx = _builder.getContext(); - auto resultType = - MatrixType::get(ctx, inputType.getRows(), DimAttr::getOne(ctx), - inputType.getSemiring()); - v = _builder.create(loc, resultType, arg); - return mlir::success(); + return parseAtomReduceRows(v); } if (name == "reduceCols") { - mlir::Value arg; - if (eatOrError(Token::LPAREN) || parseExpr(arg) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - auto inputType = llvm::cast(arg.getType()); - auto *ctx = _builder.getContext(); - auto resultType = - MatrixType::get(ctx, DimAttr::getOne(ctx), inputType.getCols(), - inputType.getSemiring()); - v = _builder.create(loc, resultType, arg); - return mlir::success(); + return parseAtomReduceCols(v); } if (name == "reduce") { - mlir::Value arg; - if (eatOrError(Token::LPAREN) || parseExpr(arg) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - auto inputType = llvm::cast(arg.getType()); - v = _builder.create(loc, inputType.asScalar(), arg); - return mlir::success(); + return parseAtomReduce(v); } if (name == "pickAny") { - mlir::Value arg; - if (eatOrError(Token::LPAREN) || parseExpr(arg) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - v = _builder.create(loc, arg); - return mlir::success(); + return parseAtomPickAny(v); } if (name == "diag") { - mlir::Value arg; - if (eatOrError(Token::LPAREN) || parseExpr(arg) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - auto argType = llvm::cast(arg.getType()); - if (!argType.isColumnVector() && !argType.isRowVector()) { - auto diag = mlir::emitError(loc) - << "diag() requires a row or column vector"; - diag.attachNote(arg.getLoc()) - << "argument has type " << typeToString(argType); - return mlir::failure(); - } - - v = _builder.create(loc, arg); - return mlir::success(); + return parseAtomDiag(v); } // TODO: Make a separate extension if (name == "tril") { - mlir::Value arg; - if (eatOrError(Token::LPAREN) || parseExpr(arg) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - v = _builder.create(loc, arg); - return mlir::success(); + return parseAtomTril(v); } // TODO: Make a separate extension if (name == "triu") { - mlir::Value arg; - if (eatOrError(Token::LPAREN) || parseExpr(arg) || - eatOrError(Token::RPAREN)) { - return mlir::failure(); - } - - v = _builder.create(loc, arg); - return mlir::success(); + return parseAtomTriu(v); } auto var = _symbolTable.lookup(name); @@ -1908,6 +1607,383 @@ static std::optional parseFloat(llvm::StringRef s) { return v; } +mlir::ParseResult Parser::parseAtomMatrix(mlir::Value &v) { + auto loc = cur().loc; + mlir::Type ring; + mlir::Value rowsExpr; + mlir::Value colsExpr; + if (eatOrError(Token::LANGLE) || parseSemiring(ring) || + eatOrError(Token::RANGLE) || eatOrError(Token::LPAREN) || + parseExpr(rowsExpr) || eatOrError(Token::COMMA) || parseExpr(colsExpr) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto rows = inferDim(rowsExpr, loc); + auto cols = inferDim(colsExpr, loc); + if (!rows || !cols) { + return mlir::failure(); + } + + v = _builder.create( + loc, _builder.getType(rows, cols, ring), + llvm::cast(ring).addIdentity()); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomVector(mlir::Value &v) { + auto loc = cur().loc; + mlir::Type ring; + mlir::Value rowsExpr; + if (eatOrError(Token::LANGLE) || parseSemiring(ring) || + eatOrError(Token::RANGLE) || eatOrError(Token::LPAREN) || + parseExpr(rowsExpr) || eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto rows = inferDim(rowsExpr, loc); + if (!rows) { + return mlir::failure(); + } + + auto *ctx = _builder.getContext(); + v = _builder.create( + loc, MatrixType::get(ctx, rows, DimAttr::getOne(ctx), ring), + llvm::cast(ring).addIdentity()); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomCast(mlir::Value &v) { + auto loc = cur().loc; + mlir::Type ring; + mlir::Value expr; + if (eatOrError(Token::LANGLE) || parseSemiring(ring) || + eatOrError(Token::RANGLE) || eatOrError(Token::LPAREN) || + parseExpr(expr) || eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto exprType = llvm::cast(expr.getType()); + auto *dialect = _builder.getContext()->getLoadedDialect(); + if (!dialect->isCastLegal(exprType.getSemiring(), ring)) { + return mlir::emitError(loc) + << "invalid cast from " << typeToString(exprType.getSemiring()) + << " to " << typeToString(ring); + } + + v = _builder.create(loc, + _builder.getType( + exprType.getRows(), exprType.getCols(), ring), + expr); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomZero(mlir::Value &v) { + auto loc = cur().loc; + mlir::Type ring; + if (eatOrError(Token::LPAREN) || parseSemiring(ring) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto value = llvm::cast(ring).addIdentity(); + v = _builder.create(loc, value); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomOne(mlir::Value &v) { + auto loc = cur().loc; + mlir::Type ring; + if (eatOrError(Token::LPAREN) || parseSemiring(ring) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto value = llvm::cast(ring).mulIdentity(); + v = _builder.create(loc, value); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomApply(mlir::Value &v) { + auto loc = cur().loc; + mlir::func::FuncOp func; + llvm::SmallVector args(1); + if (eatOrError(Token::LPAREN) || parseFuncRef(func) || + eatOrError(Token::COMMA) || parseExpr(args[0])) { + return mlir::failure(); + } + + if (cur().type == Token::COMMA) { + // Have a second arg. + auto &arg = args.emplace_back(); + if (eatOrError(Token::COMMA) || parseExpr(arg)) { + return mlir::failure(); + } + } + + if (eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + // Validate function signature + auto funcType = func.getFunctionType(); + size_t numFuncArgs = funcType.getNumInputs(); + + if (args.size() == 1) { + // Unary apply: function must have exactly 1 argument + if (numFuncArgs != 1) { + auto diag = mlir::emitError(loc) + << "apply() with 1 matrix argument requires a function " + "with 1 parameter, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + } else { + // Binary apply: function must have exactly 2 arguments + if (numFuncArgs != 2) { + auto diag = mlir::emitError(loc) + << "apply() with 2 arguments requires a function with 2 " + "parameters, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + + // Second argument must be a scalar + auto arg1Type = llvm::cast(args[1].getType()); + if (!arg1Type.isScalar()) { + auto diag = mlir::emitError(loc) + << "second argument to apply() must be a scalar"; + diag.attachNote(args[1].getLoc()) + << "argument has type " << typeToString(arg1Type); + return mlir::failure(); + } + } + + // Check that all function parameters are scalars + for (size_t i = 0; i < numFuncArgs; i++) { + auto paramType = llvm::dyn_cast(funcType.getInput(i)); + if (!paramType || !paramType.isScalar()) { + auto diag = mlir::emitError(loc) + << "apply() requires function parameters to be scalars"; + diag.attachNote(func.getLoc()) << "parameter " << i << " has type " + << typeToString(funcType.getInput(i)); + return mlir::failure(); + } + } + + if (args.size() == 1) { + v = _builder.create(loc, func, args[0]); + } else { + assert(args.size() == 2); + v = _builder.create(loc, func, args[0], args[1]); + } + + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomSelect(mlir::Value &v) { + auto loc = cur().loc; + mlir::func::FuncOp func; + llvm::SmallVector args(1); + if (eatOrError(Token::LPAREN) || parseFuncRef(func) || + eatOrError(Token::COMMA) || parseExpr(args[0])) { + return mlir::failure(); + } + + if (cur().type == Token::COMMA) { + // Have a second arg. + auto &arg = args.emplace_back(); + if (eatOrError(Token::COMMA) || parseExpr(arg)) { + return mlir::failure(); + } + } + + if (eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + // Validate function signature + auto funcType = func.getFunctionType(); + size_t numFuncArgs = funcType.getNumInputs(); + + if (args.size() == 1) { + // Unary select: function must have exactly 1 argument + if (numFuncArgs != 1) { + auto diag = mlir::emitError(loc) + << "select() with 1 matrix argument requires a function " + "with 1 parameter, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + } else { + // Binary select: function must have exactly 2 arguments + if (numFuncArgs != 2) { + auto diag = mlir::emitError(loc) + << "select() with 2 arguments requires a function with 2 " + "parameters, but got " + << numFuncArgs; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + + // Second argument must be a scalar + auto arg1Type = llvm::cast(args[1].getType()); + if (!arg1Type.isScalar()) { + auto diag = mlir::emitError(loc) + << "second argument to select() must be a scalar"; + diag.attachNote(args[1].getLoc()) + << "argument has type " << typeToString(arg1Type); + return mlir::failure(); + } + } + + // Check that all function parameters are scalars + for (size_t i = 0; i < numFuncArgs; i++) { + auto paramType = llvm::dyn_cast(funcType.getInput(i)); + if (!paramType || !paramType.isScalar()) { + auto diag = mlir::emitError(loc) + << "select() requires function parameters to be scalars"; + diag.attachNote(func.getLoc()) << "parameter " << i << " has type " + << typeToString(funcType.getInput(i)); + return mlir::failure(); + } + } + + // Check that function returns a boolean scalar + if (funcType.getNumResults() != 1) { + auto diag = mlir::emitError(loc) + << "select() requires function to return exactly one value"; + diag.attachNote(func.getLoc()) << "function defined here"; + return mlir::failure(); + } + + auto returnType = llvm::dyn_cast(funcType.getResult(0)); + auto *ctx = _builder.getContext(); + auto expectedReturnType = MatrixType::scalarOf(SemiringTypes::forBool(ctx)); + if (!returnType || returnType != expectedReturnType) { + auto diag = mlir::emitError(loc) + << "select() requires function to return bool"; + diag.attachNote(func.getLoc()) + << "function returns " << typeToString(funcType.getResult(0)); + return mlir::failure(); + } + + if (args.size() == 1) { + v = _builder.create(loc, func.getSymName(), args[0]); + } else { + assert(args.size() == 2); + v = _builder.create(loc, func.getSymName(), args[0], + args[1]); + } + + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomReduceRows(mlir::Value &v) { + auto loc = cur().loc; + mlir::Value arg; + if (eatOrError(Token::LPAREN) || parseExpr(arg) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto inputType = llvm::cast(arg.getType()); + auto *ctx = _builder.getContext(); + auto resultType = MatrixType::get( + ctx, inputType.getRows(), DimAttr::getOne(ctx), inputType.getSemiring()); + v = _builder.create(loc, resultType, arg); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomReduceCols(mlir::Value &v) { + auto loc = cur().loc; + mlir::Value arg; + if (eatOrError(Token::LPAREN) || parseExpr(arg) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto inputType = llvm::cast(arg.getType()); + auto *ctx = _builder.getContext(); + auto resultType = MatrixType::get( + ctx, DimAttr::getOne(ctx), inputType.getCols(), inputType.getSemiring()); + v = _builder.create(loc, resultType, arg); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomReduce(mlir::Value &v) { + auto loc = cur().loc; + mlir::Value arg; + if (eatOrError(Token::LPAREN) || parseExpr(arg) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto inputType = llvm::cast(arg.getType()); + v = _builder.create(loc, inputType.asScalar(), arg); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomPickAny(mlir::Value &v) { + auto loc = cur().loc; + mlir::Value arg; + if (eatOrError(Token::LPAREN) || parseExpr(arg) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + v = _builder.create(loc, arg); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomDiag(mlir::Value &v) { + auto loc = cur().loc; + mlir::Value arg; + if (eatOrError(Token::LPAREN) || parseExpr(arg) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + auto argType = llvm::cast(arg.getType()); + if (!argType.isColumnVector() && !argType.isRowVector()) { + auto diag = mlir::emitError(loc) + << "diag() requires a row or column vector"; + diag.attachNote(arg.getLoc()) + << "argument has type " << typeToString(argType); + return mlir::failure(); + } + + v = _builder.create(loc, arg); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomTril(mlir::Value &v) { + auto loc = cur().loc; + mlir::Value arg; + if (eatOrError(Token::LPAREN) || parseExpr(arg) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + v = _builder.create(loc, arg); + return mlir::success(); +} + +mlir::ParseResult Parser::parseAtomTriu(mlir::Value &v) { + auto loc = cur().loc; + mlir::Value arg; + if (eatOrError(Token::LPAREN) || parseExpr(arg) || + eatOrError(Token::RPAREN)) { + return mlir::failure(); + } + + v = _builder.create(loc, arg); + return mlir::success(); +} + mlir::ParseResult Parser::parseLiteral(mlir::Type ring, mlir::Value &v) { auto *ctx = _builder.getContext(); mlir::TypedAttr attr; diff --git a/compiler/test/parse-err/ewise-func-non-scalar-params.gr b/compiler/test/parse-err/ewise-func-non-scalar-params.gr index 6f1fed1..7e54b38 100644 --- a/compiler/test/parse-err/ewise-func-non-scalar-params.gr +++ b/compiler/test/parse-err/ewise-func-non-scalar-params.gr @@ -1,6 +1,7 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s -// expected-note@below{{parameter 0 has type Matrix}} +// expected-note@below{{first parameter has type Matrix}} +// expected-note@below{{second parameter has type int}} func matrixParam(m: Matrix, n: int) -> int { return n; } diff --git a/compiler/test/parse-err/ewise-func-type-mismatch-left.gr b/compiler/test/parse-err/ewise-func-type-mismatch-left.gr index 16d4118..f8d36c4 100644 --- a/compiler/test/parse-err/ewise-func-type-mismatch-left.gr +++ b/compiler/test/parse-err/ewise-func-type-mismatch-left.gr @@ -1,13 +1,13 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s -// expected-note@below{{first parameter has type real}} +// expected-note@below{{first parameter has semiring real}} func addReals(a: real, b: real) -> real { return a + b; } func EwiseFuncTypeMismatchLeft( - // expected-note@below{{left operand has type int}} + // expected-note@below{{left operand has semiring int}} m: Matrix, n: Matrix) -> Matrix { - // expected-error@below{{left operand type does not match first parameter type}} + // expected-error@below{{left operand semiring does not match first parameter type}} return m (.addReals) n; } diff --git a/compiler/test/parse-err/ewise-func-type-mismatch-right.gr b/compiler/test/parse-err/ewise-func-type-mismatch-right.gr index 517b2c6..8ff3258 100644 --- a/compiler/test/parse-err/ewise-func-type-mismatch-right.gr +++ b/compiler/test/parse-err/ewise-func-type-mismatch-right.gr @@ -1,14 +1,14 @@ // RUN: graphalg-translate --import-graphalg --verify-diagnostics %s -// expected-note@below{{second parameter has type real}} +// expected-note@below{{second parameter has semiring real}} func addMixed(a: int, b: real) -> real { return cast(a) + b; } func EwiseFuncTypeMismatchRight( m: Matrix, - // expected-note@below{{right operand has type int}} + // expected-note@below{{right operand has semiring int}} n: Matrix) -> Matrix { - // expected-error@below{{right operand type does not match second parameter type}} + // expected-error@below{{right operand semiring does not match second parameter type}} return m (.addMixed) n; } From bf5d6deb5d9921c0ca2fc3e5dc7873c1e72dba36 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 26 Nov 2025 10:40:39 +0000 Subject: [PATCH 9/9] Even more cleaning. --- compiler/src/graphalg/parse/Parser.cpp | 48 +- .../ewise-func-different-dimensions.gr | 14 + docs/parse-tests.md | 439 +----------------- 3 files changed, 56 insertions(+), 445 deletions(-) create mode 100644 compiler/test/parse-err/ewise-func-different-dimensions.gr diff --git a/compiler/src/graphalg/parse/Parser.cpp b/compiler/src/graphalg/parse/Parser.cpp index 000d1c6..052e895 100644 --- a/compiler/src/graphalg/parse/Parser.cpp +++ b/compiler/src/graphalg/parse/Parser.cpp @@ -177,19 +177,39 @@ class Parser { mlir::ParseResult parseBinaryOp(BinaryOp &op); + /** + * Check input types and build \c MatMulOp. + * + * @return \c nullptr if type checking fails. + */ mlir::Value buildMatMul(mlir::Location loc, mlir::Value lhs, mlir::Value rhs); + /** + * Check input types and build \c ElementWiseOp. + * + * @param mustBeScalar Whether we parsed (a b) or (a (.) b). + * + * @return \c nullptr if type checking fails. + */ mlir::Value buildElementWise(mlir::Location loc, mlir::Value lhs, BinaryOp op, mlir::Value rhs, bool mustBeScalar); - mlir::Value buildElementWiseFunc(mlir::Location loc, mlir::Value lhs, - mlir::func::FuncOp funcOp, mlir::Value rhs); - + /** + * Check input types and build \c ElementWiseApplyOp. + * + * @return \c nullptr if type checking fails. + */ + mlir::Value buildElementWiseApply(mlir::Location loc, mlir::Value lhs, + mlir::func::FuncOp funcOp, mlir::Value rhs); + + /** + * Builds \c TransposeOp, \c NrowsOp, \c NcolsOp or \c NvalsOp depending on \c + * property. + */ mlir::Value buildDotProperty(mlir::Location loc, mlir::Value value, llvm::StringRef property); mlir::ParseResult parseAtom(mlir::Value &v); - mlir::ParseResult parseAtomMatrix(mlir::Value &v); mlir::ParseResult parseAtomVector(mlir::Value &v); mlir::ParseResult parseAtomCast(mlir::Value &v); @@ -771,7 +791,7 @@ mlir::ParseResult Parser::parseStmtFor() { } } - // Parse condtion expression. + // Parse condition expression. mlir::Value result; loc = cur().loc; if (parseExpr(result) || eatOrError(Token::SEMI)) { @@ -1158,7 +1178,7 @@ mlir::ParseResult Parser::parseExpr(mlir::Value &v, int minPrec) { return mlir::failure(); } - atomLhs = buildElementWiseFunc(loc, atomLhs, funcOp, atomRhs); + atomLhs = buildElementWiseApply(loc, atomLhs, funcOp, atomRhs); if (!atomLhs) { return mlir::failure(); } @@ -1360,9 +1380,9 @@ mlir::Value Parser::buildElementWise(mlir::Location loc, mlir::Value lhs, return _builder.create(loc, lhs, op, rhs); } -mlir::Value Parser::buildElementWiseFunc(mlir::Location loc, mlir::Value lhs, - mlir::func::FuncOp funcOp, - mlir::Value rhs) { +mlir::Value Parser::buildElementWiseApply(mlir::Location loc, mlir::Value lhs, + mlir::func::FuncOp funcOp, + mlir::Value rhs) { // Validate element-wise function application auto funcType = funcOp.getFunctionType(); @@ -1393,6 +1413,16 @@ mlir::Value Parser::buildElementWiseFunc(mlir::Location loc, mlir::Value lhs, return nullptr; } + // Check that operand dimensions match + if (lhsType.getDims() != rhsType.getDims()) { + auto diag = mlir::emitError(loc) << "operands have different dimensions"; + diag.attachNote(lhs.getLoc()) + << "left operand has dimensions " << dimsToString(lhsType.getDims()); + diag.attachNote(rhs.getLoc()) + << "right operand has dimensions " << dimsToString(rhsType.getDims()); + return nullptr; + } + // Check that operand semirings match function parameter semirings if (lhsType.getSemiring() != param0Type.getSemiring()) { auto diag = mlir::emitError(loc) diff --git a/compiler/test/parse-err/ewise-func-different-dimensions.gr b/compiler/test/parse-err/ewise-func-different-dimensions.gr new file mode 100644 index 0000000..fa6c2ec --- /dev/null +++ b/compiler/test/parse-err/ewise-func-different-dimensions.gr @@ -0,0 +1,14 @@ +// RUN: graphalg-translate --import-graphalg --verify-diagnostics %s + +func addInts(a: int, b: int) -> int { + return a + b; +} + +func EwiseFuncDifferentDimensions( + // expected-note@below{{left operand has dimensions (s x s)}} + m: Matrix, + // expected-note@below{{right operand has dimensions (t x t)}} + n: Matrix) -> Matrix { + // expected-error@below{{operands have different dimensions}} + return m (.addInts) n; +} diff --git a/docs/parse-tests.md b/docs/parse-tests.md index 1370f2c..3eaeaab 100644 --- a/docs/parse-tests.md +++ b/docs/parse-tests.md @@ -72,7 +72,7 @@ cmake --build compiler/build --target graphalg-translate ## Common Error Categories -### 1. Duplicate Definitions +### Duplicate Definitions **Function names** (`func-name-dup.gr`): ```graphalg @@ -92,51 +92,7 @@ func Dup( a: int) -> int { return a; } ``` -### 2. Fill Syntax Errors - -**Vector fill on non-vector** (`vector-fill-row-vector.gr`): -```graphalg -func Test(m: Matrix<1, s, int>, x: int) -> Matrix<1, s, int> { - // expected-error@below{{vector fill [:] used with non-vector base}} - // expected-note@below{{base has type Matrix<1, s, int>}} - m[:] = x; - return m; -} -``` - -**Matrix fill on vector** (`matrix-fill-col-vector.gr`): -```graphalg -func Test(v: Vector, x: int) -> Vector { - // expected-error@below{{matrix fill [:, :] used with column vector base}} - // expected-note@below{{base has type Vector}} - v[:, :] = x; - return v; -} -``` - -**Non-scalar fill expression** (`vector-fill-non-scalar.gr`): -```graphalg -func Test(v: Vector, e: Vector) -> Vector { - // expected-error@below{{fill expression is not a scalar}} - v[:] = e; - return v; -} -``` - -### 3. Masked Assignment Errors - -**Dimension mismatch** (`mask-dimension-mismatch.gr`): -```graphalg -func Test(a: Matrix, m: Matrix, e: Matrix) -> Matrix { - // expected-error@below{{base dimensions do not match the dimensions of the mask}} - // expected-note@below{{base dimension: (s x s)}} - // expected-note@below{{mask dimensions: (t x t)}} - a = e; - return a; -} -``` - -### 4. Type Errors +### Type Errors **Reassignment with different type** (`reassign-type-mismatch.gr`): ```graphalg @@ -159,7 +115,7 @@ func Test() -> int { } ``` -### 5. Variable Scoping +### Variable Scoping **Undefined variable** (`accum-undefined.gr`): ```graphalg @@ -184,45 +140,6 @@ func Test() -> int { } ``` -### 6. For Loop Errors - -**Non-integer range bounds** (`loop-range-start-non-int.gr`, `loop-range-end-non-int.gr`): -```graphalg -func Test() -> int { - a = int(0); - // expected-error@below{{loop range start must be an integer, but got real}} - for i in real(1.0):int(10) { - a = a + int(1); - } - return a; -} -``` - -**Non-dimension range** (`loop-range-not-dimension.gr`): -```graphalg -func Test(m: Matrix) -> int { - a = int(0); - // expected-error@below{{not a dimension type}} - // expected-note@below{{defined here}} - for i in m.nvals { - a = a + int(1); - } - return a; -} -``` - -**Non-boolean until condition** (`loop-until-non-bool.gr`): -```graphalg -func Test() -> int { - a = int(0); - for i in int(1):int(10) { - a = a + int(1); - // expected-error@below{{loop condition does not produce a boolean scalar, got int}} - } until int(5); - return a; -} -``` - ## Type Formatting in Error Messages The parser formats types in a user-friendly way: @@ -251,333 +168,6 @@ When adding new error detection to the parser: 6. **Run the test** to verify the exact error message format 7. **Update the test** with the correct expected message -### Example: Adding Loop Range Type Checks - -```cpp -// Check that begin is an integer scalar -auto intScalarType = MatrixType::scalarOf(SemiringTypes::forInt(_builder.getContext())); -if (r.begin.getType() != intScalarType) { - return mlir::emitError(beginLoc) - << "loop range start must be an integer, but got " - << typeToString(r.begin.getType()); -} -``` - -### 7. Return Statement Errors - -**Return inside loop** (`return-in-loop.gr`): -```graphalg -func Test() -> int { - for i in int(1):int(10) { - // expected-error@below{{return statement inside a loop is not allowed}} - return int(5); - } - return int(0); -} -``` - -**Return not last statement** (`return-not-last.gr`): -```graphalg -func Test() -> int { - return int(42); - // expected-error@below{{statement after return is not allowed}} - a = int(5); - return a; -} -``` - -**Return wrong type** (`return-wrong-type.gr`): -```graphalg -func Test() -> int { - // expected-error@below{{return type mismatch: expected int, but got real}} - return real(3.14); -} -``` - -**Multiple return statements** (`return-multiple.gr`): -```graphalg -func Test() -> int { - a = int(42); - return a; - // expected-error@below{{statement after return is not allowed}} - return int(5); -} -``` - -**Missing return statement** (`return-missing.gr`): -```graphalg -// expected-error@below{{function must have a return statement}} -func Test() -> int { - a = int(42); -} -``` - -### 8. Matrix Multiplication Errors - -**Dimension mismatch** (`matmul-dimension-mismatch.gr`): -```graphalg -func MatMulDimensionMismatch( - // expected-note@below{{left side has dimensions (r x s)}} - a: Matrix, - // expected-note@below{{right side has dimensions (t x u)}} - b: Matrix) -> Matrix { - // expected-error@below{{incompatible dimensions for matrix multiply}} - return a * b; -} -``` - -### 9. Built-in Function Errors - -**diag() with non-vector** (`diag-not-vector.gr`): -```graphalg -func DiagNotVector( - // expected-note@below{{argument has type Matrix}} - m: Matrix) -> Matrix { - // expected-error@below{{diag() requires a row or column vector}} - return diag(m); -} -``` - -**apply() with wrong function signature** (`apply-unary-func-wrong-arg-count.gr`): -```graphalg -// expected-note@below{{function defined here}} -func twoArgs(a: int, b: int) -> int { - return a + b; -} - -func ApplyUnaryFuncWrongArgCount(m: Matrix) -> Matrix { - // expected-error@below{{apply() with 1 matrix argument requires a function with 1 parameter, but got 2}} - return apply(twoArgs, m); -} -``` - -**apply() with non-scalar function parameters** (`apply-func-non-scalar-args.gr`): -```graphalg -// expected-note@below{{parameter 0 has type Matrix}} -func nonScalarFunc(m: Matrix) -> int { - return int(0); -} - -func ApplyFuncNonScalarArgs(m: Matrix) -> Matrix { - // expected-error@below{{apply() requires function parameters to be scalars}} - return apply(nonScalarFunc, m); -} -``` - -**select() with non-bool return type** (`select-func-non-bool-return.gr`): -```graphalg -// expected-note@below{{function returns int}} -func returnsInt(a: int) -> int { - return a; -} - -func SelectFuncNonBoolReturn(m: Matrix) -> Matrix { - // expected-error@below{{select() requires function to return bool}} - return select(returnsInt, m); -} -``` - -### 10. Element-wise Operation Errors - -**Different semirings** (`ewise-different-semirings.gr`): -```graphalg -func EwiseDifferentSemirings( - // expected-note@below{{left operand has semiring int}} - a: Matrix, - // expected-note@below{{right operand has semiring real}} - b: Matrix) -> Matrix { - // expected-error@below{{element-wise operation requires operands to have the same semiring}} - return a (.+) b; -} -``` - -**Different dimensions** (`ewise-different-dimensions.gr`): -```graphalg -func EwiseDifferentDimensions( - // expected-note@below{{left operand has dimensions (s x s)}} - a: Matrix, - // expected-note@below{{right operand has dimensions (t x t)}} - b: Matrix) -> Matrix { - // expected-error@below{{element-wise operation requires operands to have the same dimensions}} - return a (.+) b; -} -``` - -### 11. Subtraction and Negation Errors - -**Subtraction with unsupported semiring** (`sub-bool-unsupported.gr`, `sub-trop-int-unsupported.gr`): -```graphalg -func SubBoolUnsupported( - // expected-note@below{{operands have semiring bool}} - a: bool, b: bool) -> bool { - // expected-error@below{{subtraction is only supported for int and real types}} - return a - b; -} -``` - -**Subtraction with non-scalars** (`sub-matrix-not-scalar.gr`): -```graphalg -func SubMatrixNotScalar( - // expected-note@below{{left operand has type Matrix}} - a: Matrix, - // expected-note@below{{right operand has type Matrix}} - b: Matrix) -> Matrix { - // Note: Use element-wise subtraction (.-) for matrices - // expected-error@below{{subtraction only works on scalars; use element-wise subtraction (.-) for matrices}} - return a - b; -} -``` - -**Negation with unsupported semiring** (`neg-bool-unsupported.gr`, `neg-trop-int-unsupported.gr`): -```graphalg -func NegBoolUnsupported( - // expected-note@below{{operand has semiring bool}} - a: bool) -> bool { - // expected-error@below{{negation is only supported for int and real types}} - return -a; -} -``` - -## Current Test Coverage - -As of now, we have 173 parser tests covering: -- Duplicate definitions (functions and parameters) -- Fill syntax errors (vector vs matrix, non-scalar expressions) -- Masked assignment errors (dimension mismatches) -- Variable reassignment errors (type mismatches) -- Accumulate errors (undefined variable, type mismatches) -- Variable scoping (loop-local variables) -- For loop errors (non-integer start/end, non-dimension range, non-boolean until condition) -- Return statement errors (in loop, not last, wrong type, multiple returns, missing return) -- Matrix multiplication dimension mismatches -- Built-in function validation (diag, apply, select) -- Element-wise operation type checking (semiring and dimension compatibility) -- Subtraction and negation semiring restrictions (only int and real) -- **Comparison operator semiring restrictions (only int, real, and trop_real)** -- **Division semiring restrictions (only real and trop_int)** -- **NOT operator type restrictions (only bool)** -- **Literal type conversion validation** -- **Element-wise function application validation (parameter count, types, and existence)** -### 12. Comparison Operator Errors - -**Comparison with unsupported semiring** (`cmp-bool-unsupported.gr`, `cmp-trop-int-unsupported.gr`): -```graphalg -func CmpBoolUnsupported( - // expected-note@below{{operands have semiring bool}} - a: bool, b: bool) -> bool { - // expected-error@below{{comparison is only supported for int, real, and trop_real types}} - return a < b; -} -``` - -Comparison operators (`<`, `>`, `<=`, `>=`) only support int, real, and trop_real semirings. They are not supported for bool or trop_int. - -### 13. Division Errors - -**Division with unsupported semiring** (`div-int-unsupported.gr`, `div-trop-real-unsupported.gr`, `div-bool-unsupported.gr`): -```graphalg -func DivIntUnsupported( - // expected-note@below{{operands have semiring int}} - a: int, b: int) -> int { - // expected-error@below{{division is only supported for real and trop_int types}} - return a / b; -} -``` - -Division only supports real and trop_int semirings. It is not supported for int, bool, or trop_real. - -### 14. NOT Operator Errors - -**NOT with non-bool semiring** (`not-int-unsupported.gr`, `not-real-unsupported.gr`, `not-trop-int-unsupported.gr`): -```graphalg -func NotIntUnsupported( - // expected-note@below{{operand has semiring int}} - a: int) -> int { - // expected-error@below{{not operator is only supported for bool type}} - return !a; -} -``` - -The NOT operator (`!`) only works with bool type. It is not supported for any other semiring. - -### 15. Literal Type Conversion Errors - -**Invalid literal conversions** (`literal-bool-from-int.gr`, `literal-int-from-real.gr`, `literal-trop-real-from-bool.gr`): -```graphalg -func LiteralBoolFromInt() -> bool { - // expected-error@below{{expected 'true' or 'false'}} - return bool(42); -} - -func LiteralIntFromReal() -> int { - // expected-error@below{{expected an integer value}} - return int(42.0); -} - -func LiteralTropRealFromBool() -> trop_real { - // expected-error@below{{expected a floating-point value}} - return trop_real(false); -} -``` - -Each semiring type requires a specific literal format: -- `bool` requires `true` or `false` -- `int` and `trop_int` require integer literals -- `real` and `trop_real` require floating-point literals - -### 16. Element-wise Function Application Errors - -Element-wise function application uses the syntax `a (.func) b` to apply a binary function element-wise to two matrices. - -**Wrong parameter count** (`ewise-func-wrong-param-count.gr`): -```graphalg -// expected-note@below{{function defined here}} -func oneParam(a: int) -> int { - return a; -} - -func Test(m: Matrix, n: Matrix) -> Matrix { - // expected-error@below{{element-wise function application requires a function with 2 parameters, but got 1}} - return m (.oneParam) n; -} -``` - -**Non-scalar parameters** (`ewise-func-non-scalar-params.gr`): -```graphalg -// expected-note@below{{parameter 0 has type Matrix}} -func matrixParam(m: Matrix, n: int) -> int { - return n; -} - -func Test(m: Matrix, n: Matrix) -> Matrix { - // expected-error@below{{element-wise function application requires function parameters to be scalars}} - return m (.matrixParam) n; -} -``` - -**Type mismatch** (`ewise-func-type-mismatch-left.gr`, `ewise-func-type-mismatch-right.gr`): -```graphalg -// expected-note@below{{first parameter has type real}} -func addReals(a: real, b: real) -> real { - return a + b; -} - -func Test( - // expected-note@below{{left operand has type int}} - m: Matrix, n: Matrix) -> Matrix { - // expected-error@below{{left operand type does not match first parameter type}} - return m (.addReals) n; -} -``` - -**Undefined function** (`ewise-func-undefined.gr`): -```graphalg -func Test(m: Matrix, n: Matrix) -> Matrix { - // expected-error@below{{unknown function 'doesNotExist'}} - return m (.doesNotExist) n; -} -``` - ## Best Practices for Error Messages ### Always Use TypeFormatter for Type Display @@ -599,26 +189,3 @@ auto diag = mlir::emitError(loc) The `typeToString()` function uses `TypeFormatter` internally, which formats types in a user-friendly way: - Raw MLIR: `!graphalg.mat x distinct[0]<> x i64>` - Formatted: `Matrix` - -### Semiring Type Restrictions Summary - -| Operation | Supported Semirings | Notes | -|-----------|-------------------|-------| -| Addition (`+`) | All | No restrictions | -| Subtraction (`-`) | int, real | Scalar only without `(.-)` | -| Multiplication (`*`) | All | Matrix multiplication has dimension requirements | -| Division (`/`) | real, trop_int | | -| Comparison (`<`, `>`, `<=`, `>=`) | int, real, trop_real | | -| Equality (`==`, `!=`) | All | No restrictions | -| NOT (`!`) | bool | Only boolean values | -| Negation (`-x`) | int, real | Unary operator | - -### Validation Checklist for Element-wise Function Application - -When implementing validation for element-wise function application `a (.func) b`: - -1. ✅ Function must exist (checked by `parseFuncRef`) -2. ✅ Function must have exactly 2 parameters -3. ✅ All function parameters must be scalars -4. ✅ Left operand type must match first parameter type -5. ✅ Right operand type must match second parameter type