lang: Parse signs as unary operators

16d5efcb1fdfb531a448d42d8442f974fc9e495b992955f80fae2f1f252a890e
Previously, the scanner folded adjacent signs into numeric tokens, so
expression tokenization depended on whitespace and subtraction could be
misread as a signed literal.

Now numeric literals contain only an unsigned magnitude, while the
parser represents negation explicitly. This makes tokenization
context-free and preserves literal range checking through unary
expressions.

Assisted-by: Codex:gpt-5.6-sol
Alexis Sellier committed ago 1 parent 5579ed17
lib/std/arch/rv64/asm/parser.rad +7 -2
825 825
    return value;
826 826
}
827 827
828 828
/// Parse integer literal text as an i64.
829 829
fn parseIntegerText(text: *[u8]) -> ?i64 {
830 -
    let literal = try fmt::parseInt(text) catch {
830 +
    if text.len == 0 {
831 +
        return nil;
832 +
    }
833 +
    let negative = text[0] == '-';
834 +
    let magnitudeText = &text[1..] if negative or text[0] == '+' else text;
835 +
    let literal = try fmt::parseInt(magnitudeText) catch {
831 836
        return nil;
832 837
    };
833 -
    if literal.negative {
838 +
    if negative {
834 839
        if literal.magnitude > parser::I64_MIN_MAGNITUDE {
835 840
            return nil;
836 841
        }
837 842
        if literal.magnitude == parser::I64_MIN_MAGNITUDE {
838 843
            return parser::I64_MIN;
lib/std/fmt.rad +3 -19
36 36
37 37
/// Parsed integer literal metadata.
38 38
export record IntLiteral {
39 39
    /// Raw characters that comprised the literal.
40 40
    text: *[u8],
41 -
    /// Absolute magnitude parsed from the literal.
41 +
    /// Magnitude parsed from the literal.
42 42
    magnitude: u64,
43 43
    /// Radix used by the literal.
44 44
    radix: Radix,
45 -
    /// Whether the literal spelled an explicit sign.
46 -
    signed: bool,
47 -
    /// Whether the literal used a negative sign.
48 -
    negative: bool,
49 45
}
50 46
51 47
/// Format a u32 by writing it to the provided buffer.
52 48
export fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] {
53 49
    assert buffer.len >= U32_STR_LEN;
208 204
        case '0'  => return 0,
209 205
        else      => return ch,
210 206
    }
211 207
}
212 208
213 -
/// Parse an integer literal (binary, decimal, or hexadecimal) including an optional sign.
209 +
/// Parse an unsigned integer literal (binary, decimal, or hexadecimal).
214 210
export fn parseInt(text: *[u8]) -> IntLiteral throws (ParseError) {
215 211
    if text.len == 0 {
216 212
        throw ParseError::Invalid;
217 213
    }
218 -
    let first = text[0];
219 -
    let negative = first == '-';
220 -
    let signed: bool = negative or (first == '+');
221 214
222 215
    let mut start: u32 = 0;
223 216
    let mut radix: u32 = 10;
224 217
    let mut radixType = Radix::Decimal;
225 -
226 -
    if signed {
227 -
        set start = 1;
228 -
        if start >= text.len {
229 -
            throw ParseError::Invalid;
230 -
        }
231 -
    }
232 218
    if start + 1 < text.len and text[start] == '0' {
233 219
        let prefix = text[start + 1];
234 220
        if prefix == 'x' or prefix == 'X' {
235 221
            set radix = 16;
236 222
            set radixType = Radix::Hex;
259 245
        if value > U64_MAX - (digit as u64) {
260 246
            throw ParseError::Overflow;
261 247
        }
262 248
        set value += (digit as u64);
263 249
    }
264 -
    return IntLiteral {
265 -
        text, magnitude: value, radix: radixType, signed, negative,
266 -
    };
250 +
    return IntLiteral { text, magnitude: value, radix: radixType };
267 251
}
268 252
269 253
/// Process escape sequences in a raw string, writing the result into `dst`.
270 254
/// Returns the number of bytes written.
271 255
export fn unescapeString(raw: *[u8], dst: *mut [u8]) -> u32 {
lib/std/lang/lower.rad +6 -2
6026 6026
    return val;
6027 6027
}
6028 6028
6029 6029
/// Lower a unary operation.
6030 6030
fn lowerUnOp(self: *mut FnLowerer, node: *ast::Node, unop: ast::UnOp) -> il::Val throws (LowerError) {
6031 +
    if unop.op == ast::UnaryOp::Neg {
6032 +
        if let case ast::NodeValue::Number(lit) = unop.value.value {
6033 +
            return il::Val::Imm((0 - lit.magnitude) as i64);
6034 +
        }
6035 +
    }
6031 6036
    let val = try lowerExpr(self, unop.value);
6032 6037
    let t = try typeOf(self, node);
6033 6038
    let typ = ilType(self.low, t);
6034 6039
    let dst = nextReg(self);
6035 6040
    let mut needsExt: bool = false;
7065 7070
        }
7066 7071
        case ast::NodeValue::ScopeAccess(_) => {
7067 7072
            set val = try lowerScopeAccess(self, node);
7068 7073
        }
7069 7074
        case ast::NodeValue::Number(lit) => {
7070 -
            let mag = -(lit.magnitude as i64) if lit.negative else lit.magnitude as i64;
7071 -
            set val = il::Val::Imm(mag);
7075 +
            set val = il::Val::Imm(lit.magnitude as i64);
7072 7076
        }
7073 7077
        case ast::NodeValue::Bool(b) => {
7074 7078
            set val = il::Val::Imm(1) if b else il::Val::Imm(0);
7075 7079
        }
7076 7080
        case ast::NodeValue::Char(c) => {
lib/std/lang/parser.rad +1 -1
1322 1322
{
1323 1323
    try expect(p, scanner::TokenKind::Try, "expected `try`");
1324 1324
1325 1325
    let shouldPanic = consume(p, scanner::TokenKind::Bang);
1326 1326
    let returnsOptional = consume(p, scanner::TokenKind::Question);
1327 -
    let expr = try parsePrimary(p);
1327 +
    let expr = try parseUnaryExpr(p);
1328 1328
    let mut catches = ast::nodeSlice(p.arena, 4);
1329 1329
1330 1330
    while consume(p, scanner::TokenKind::Catch) {
1331 1331
        let mut binding: ?*ast::Node = nil;
1332 1332
        let mut typeNode: ?*ast::Node = nil;
lib/std/lang/parser/tests.rad +27 -34
279 279
    let lit = try parseNumberLiteral("1234");
280 280
    try testing::expect(lit.magnitude == 1234);
281 281
    try testing::expect(lit.radix == fmt::Radix::Decimal);
282 282
}
283 283
284 -
/// Verify that hexadecimal literals record metadata without marking them signed.
284 +
/// Verify that hexadecimal literals record magnitude and radix metadata.
285 285
@test fn testParseNumberMetadata() throws (testing::TestError) {
286 286
    let lit = try parseNumberLiteral("0xFF");
287 287
    try testing::expect(lit.magnitude == 0xFF);
288 288
    try testing::expect(lit.radix == fmt::Radix::Hex);
289 -
    try testing::expect(not lit.signed);
290 -
    try testing::expect(not lit.negative);
291 289
}
292 290
293 291
/// Verify that binary literals capture their radix.
294 292
@test fn testParseBinaryLiteralMetadata() throws (testing::TestError) {
295 293
    let lit = try parseNumberLiteral("0b1010");
296 294
    try testing::expect(lit.magnitude == 0b1010);
297 295
    try testing::expect(lit.radix == fmt::Radix::Binary);
298 296
}
299 297
300 -
/// Signed literals produced by the scanner keep sign details in metadata.
301 -
@test fn testParseSignedLiteralMetadata() throws (testing::TestError) {
302 -
    let literal = try parseNumberLiteral("42");
303 -
    try testing::expect(not literal.signed);
304 -
    try testing::expect(not literal.negative);
305 -
    try testing::expect(literal.magnitude == 42);
306 -
307 -
    let neg = try parseNumberLiteral("-99");
308 -
    try testing::expect(neg.signed);
309 -
    try testing::expect(neg.negative);
310 -
    try testing::expect(neg.magnitude == 99);
298 +
/// Negative literals parse as unary negation of an unsigned number.
299 +
@test fn testParseNegativeLiteral() throws (testing::TestError) {
300 +
    let node = try! parseExprStr("-99");
301 +
    let case ast::NodeValue::UnOp(neg) = node.value
302 +
        else throw testing::TestError::Failed;
303 +
    try testing::expect(neg.op == ast::UnaryOp::Neg);
304 +
    let case ast::NodeValue::Number(lit) = neg.value.value
305 +
        else throw testing::TestError::Failed;
306 +
    try testing::expect(lit.magnitude == 99);
311 307
}
312 308
313 -
/// Literals with prefixes still parse correctly when explicitly signed.
314 -
@test fn testParseSignedPrefixedLiteral() throws (testing::TestError) {
315 -
    let hex = try parseNumberLiteral("+0x2A");
316 -
    try testing::expect(hex.signed);
317 -
    try testing::expect(not hex.negative);
318 -
    try testing::expect(hex.radix == fmt::Radix::Hex);
319 -
    try testing::expect(hex.magnitude == 0x2A);
320 -
321 -
    let neg = try parseNumberLiteral("-0x2A");
322 -
    try testing::expect(neg.signed);
323 -
    try testing::expect(neg.negative);
324 -
    try testing::expect(neg.radix == fmt::Radix::Hex);
325 -
    try testing::expect(neg.magnitude == 0x2A);
326 -
327 -
    let bin = try parseNumberLiteral("-0b11");
328 -
    try testing::expect(bin.signed);
329 -
    try testing::expect(bin.negative);
330 -
    try testing::expect(bin.radix == fmt::Radix::Binary);
331 -
    try testing::expect(bin.magnitude == 0b11);
309 +
/// Unary plus is not part of the expression grammar.
310 +
@test fn testRejectUnaryPlus() throws (testing::TestError) {
311 +
    try expectNumberLiteralFail("+1");
312 +
    try expectNumberLiteralFail("+value");
313 +
    try expectNumberLiteralFail("+(value)");
332 314
}
333 315
334 316
/// Range expressions parse with explicit start and end bounds.
335 317
@test fn testParseRangeExpr() throws (testing::TestError) {
336 318
    let node = try! parseExprStr("0..5");
354 336
    // 2^64 overflows u64.
355 337
    try expectNumberLiteralFail("18446744073709551616");
356 338
    try expectNumberLiteralFail("0x10000000000000000");
357 339
    try expectNumberLiteralFail("0x1G");
358 340
    try expectNumberLiteralFail("0b102");
359 -
    try expectNumberLiteralFail("+0x1G");
360 341
}
361 342
362 343
/// Test parsing nil literal.
363 344
@test fn testParseNil() throws (testing::TestError) {
364 345
    let r1 = try! parseExprStr("nil");
1416 1397
    try expectIdent(node.expr, "value");
1417 1398
    try testing::expect(node.catches.len == 0);
1418 1399
    try testing::expect(not node.shouldPanic);
1419 1400
}
1420 1401
1402 +
/// Test that `try?` consumes a unary operand.
1403 +
@test fn testParseTryOptionalUnary() throws (testing::TestError) {
1404 +
    let root = try! parseExprStr("try? -value");
1405 +
    let case ast::NodeValue::Try(node) = root.value
1406 +
        else throw testing::TestError::Failed;
1407 +
    let case ast::NodeValue::UnOp(neg) = node.expr.value
1408 +
        else throw testing::TestError::Failed;
1409 +
    try testing::expect(neg.op == ast::UnaryOp::Neg);
1410 +
    try expectIdent(neg.value, "value");
1411 +
    try testing::expect(node.returnsOptional);
1412 +
}
1413 +
1421 1414
/// Test parsing a `try!` expression that panics on error.
1422 1415
@test fn testParseTryBang() throws (testing::TestError) {
1423 1416
    let root = try! parseExprStr("try! value");
1424 1417
    let case ast::NodeValue::Try(node) = root.value
1425 1418
        else throw testing::TestError::Failed;
lib/std/lang/resolver.rad +14 -8
1790 1790
                // For unsuffixed integer expressions (`Type::Int`), only
1791 1791
                // validate literals directly written by the programmer.
1792 1792
                // Folded results (e.g. `0 - 65`) may not fit the target
1793 1793
                // type but are valid wrapping arithmetic at runtime.
1794 1794
                if let value = constValueEntry(self, rval) {
1795 -
                    if from <> Type::Int or isNumberLiteral(rval) {
1795 +
                    if from <> Type::Int or isIntegerLiteralExpr(rval) {
1796 1796
                        if validateConstIntRange(value, to) {
1797 1797
                            return Coercion::Identity;
1798 1798
                        }
1799 1799
                        return nil;
1800 1800
                    }
2673 2673
        },
2674 2674
        case ast::NodeValue::Number(lit) => {
2675 2675
            setNodeConstValue(self, node, ConstValue::Int(ConstInt {
2676 2676
                magnitude: lit.magnitude,
2677 2677
                bits: 64,
2678 -
                signed: lit.signed,
2679 -
                negative: lit.negative,
2678 +
                signed: false,
2679 +
                negative: false,
2680 2680
            }));
2681 2681
            return setNodeType(self, node, Type::Int);
2682 2682
        },
2683 2683
        case ast::NodeValue::Placeholder => {
2684 2684
            return setNodeType(self, node, hint);
2798 2798
    setNodeType(self, decl.value, bindingTy);
2799 2799
2800 2800
    return Type::Void;
2801 2801
}
2802 2802
2803 -
/// Check whether a node is a number literal.
2804 -
fn isNumberLiteral(node: *ast::Node) -> bool {
2805 -
    if let case ast::NodeValue::Number(_) = node.value {
2806 -
        return true;
2803 +
/// Check whether a node is an integer literal, optionally under unary negation.
2804 +
fn isIntegerLiteralExpr(node: *ast::Node) -> bool {
2805 +
    match node.value {
2806 +
        case ast::NodeValue::Number(_) => return true,
2807 +
        case ast::NodeValue::UnOp(unop) => {
2808 +
            if unop.op == ast::UnaryOp::Neg {
2809 +
                return isIntegerLiteralExpr(unop.value);
2810 +
            }
2811 +
            return false;
2812 +
        },
2813 +
        else => return false,
2807 2814
    }
2808 -
    return false;
2809 2815
}
2810 2816
2811 2817
/// Determine whether a node represents a compile-time constant expression.
2812 2818
export fn isConstExpr(self: *Resolver, node: *ast::Node) -> bool {
2813 2819
    match node.value {
lib/std/lang/resolver/tests.rad +4 -0
1790 1790
    try expectAnalyzeOk("let x: i16 = -32768;");
1791 1791
    try expectAnalyzeOk("let x: u16 = 0xFFFF;");
1792 1792
    try expectAnalyzeOk("let x: i32 = 2147483647;");
1793 1793
    try expectAnalyzeOk("let x: i32 = -2147483648;");
1794 1794
    try expectAnalyzeOk("let x: u32 = 0xFFFFFFFF;");
1795 +
    try expectAnalyzeOk("let x: i64 = 9223372036854775807;");
1796 +
    try expectAnalyzeOk("let x: i64 = -9223372036854775808;");
1795 1797
1796 1798
    try expectAnalyzeOk("constant LIMIT: u8 = 0xFF;");
1797 1799
1798 1800
    try expectIntMismatch("let x: i8 = 128;", super::Type::I8);
1799 1801
    try expectIntMismatch("let x: i8 = -129;", super::Type::I8);
1809 1811
    try expectIntMismatch("let x: i32 = 2147483648;", super::Type::I32);
1810 1812
    try expectIntMismatch("let x: i32 = -2147483649;", super::Type::I32);
1811 1813
    try expectIntMismatch("let x: i32 = 0xFFFFFFFF;", super::Type::I32);
1812 1814
    try expectIntMismatch("let x: u32 = -1;", super::Type::U32);
1813 1815
    try expectIntMismatch("let x: u32 = 0x100000000;", super::Type::U32);
1816 +
    try expectIntMismatch("let x: i64 = 9223372036854775808;", super::Type::I64);
1817 +
    try expectIntMismatch("let x: i64 = -9223372036854775809;", super::Type::I64);
1814 1818
    try expectIntMismatch("constant LIMIT: u8 = 512;", super::Type::U8);
1815 1819
    try expectIntMismatch("constant LIMIT: u8 = -5;", super::Type::U8);
1816 1820
}
1817 1821
1818 1822
@test fn testNilCoercions() throws (testing::TestError) {
lib/std/lang/scanner.rad +0 -11
301 301
    }
302 302
}
303 303
304 304
/// Scan numeric literal (decimal, hex, or binary).
305 305
fn scanNumber(s: *mut Scanner) -> Token {
306 -
    let first = s.source[s.cursor - 1];
307 -
    if first == '-' or first == '+' {
308 -
        advance(s);
309 -
    }
310 306
    // Check for hex literal (`0x` or `0X` prefix).
311 307
    if s.source[s.cursor - 1] == '0' {
312 308
        if let ch = current(s); ch == 'x' or ch == 'X' {
313 309
            advance(s);
314 310
            // Must have at least one hex digit after `0x`.
461 457
                return tok(s, TokenKind::Arrow);
462 458
            }
463 459
            if consume(s, '=') {
464 460
                return tok(s, TokenKind::MinusEqual);
465 461
            }
466 -
            // If followed by a digit, scan as negative number
467 -
            if let ch = current(s); char::isDigit(ch) {
468 -
                return scanNumber(s);
469 -
            }
470 462
            return tok(s, TokenKind::Minus);
471 463
        }
472 464
        case '+' => {
473 465
            if consume(s, '=') {
474 466
                return tok(s, TokenKind::PlusEqual);
475 467
            }
476 -
            if let ch = current(s); char::isDigit(ch) {
477 -
                return scanNumber(s);
478 -
            }
479 468
            return tok(s, TokenKind::Plus);
480 469
        }
481 470
        case '/' => {
482 471
            if consume(s, '=') {
483 472
                return tok(s, TokenKind::SlashEqual);
lib/std/lang/scanner/tests.rad +20 -24
198 198
    try testing::expect(tok.source.len == 7);
199 199
}
200 200
201 201
@test fn testScanNumbers() throws (testing::TestError) {
202 202
    let mut s = testScanner(
203 -
        "9841029 45.67 -128 +0x2A +0b11"
203 +
        "2 -2 +2 value-2 value+2 value--2 value+-2 value-+2 f(-2)"
204 204
    );
205 -
    let mut tok: super::Token = super::next(&mut s);
206 -
    try testing::expect(tok.kind == super::TokenKind::Number);
207 -
    try testing::expect(mem::eq(tok.source, "9841029"));
208 -
    try testing::expect(tok.source.len == 7);
209 -
210 -
    set tok = super::next(&mut s);
211 -
    try testing::expect(tok.kind == super::TokenKind::Number);
212 -
    try testing::expect(tok.source.len == 5);
213 -
214 -
    set tok = super::next(&mut s);
215 -
    try testing::expect(tok.kind == super::TokenKind::Number);
216 -
    try testing::expect(mem::eq(tok.source, "-128"));
217 -
    try testing::expect(tok.source.len == 4);
218 -
219 -
    set tok = super::next(&mut s);
220 -
    try testing::expect(tok.kind == super::TokenKind::Number);
221 -
    try testing::expect(mem::eq(tok.source, "+0x2A"));
222 -
    try testing::expect(tok.source.len == 5);
223 -
224 -
    set tok = super::next(&mut s);
225 -
    try testing::expect(tok.kind == super::TokenKind::Number);
226 -
    try testing::expect(mem::eq(tok.source, "+0b11"));
227 -
    try testing::expect(tok.source.len == 5);
205 +
    let expected: [super::TokenKind; 29] = [
206 +
        super::TokenKind::Number,
207 +
        super::TokenKind::Minus, super::TokenKind::Number,
208 +
        super::TokenKind::Plus, super::TokenKind::Number,
209 +
        super::TokenKind::Ident, super::TokenKind::Minus, super::TokenKind::Number,
210 +
        super::TokenKind::Ident, super::TokenKind::Plus, super::TokenKind::Number,
211 +
        super::TokenKind::Ident, super::TokenKind::Minus, super::TokenKind::Minus,
212 +
            super::TokenKind::Number,
213 +
        super::TokenKind::Ident, super::TokenKind::Plus, super::TokenKind::Minus,
214 +
            super::TokenKind::Number,
215 +
        super::TokenKind::Ident, super::TokenKind::Minus, super::TokenKind::Plus,
216 +
            super::TokenKind::Number,
217 +
        super::TokenKind::Ident, super::TokenKind::LParen, super::TokenKind::Minus,
218 +
            super::TokenKind::Number, super::TokenKind::RParen,
219 +
        super::TokenKind::Eof,
220 +
    ];
221 +
    for expectedKind in expected {
222 +
        try testing::expect(super::next(&mut s).kind == expectedKind);
223 +
    }
228 224
}
229 225
230 226
@test fn testScanKeywords() throws (testing::TestError) {
231 227
    let mut s = testScanner("nil mod not static");
232 228
    let tok1: super::Token = super::next(&mut s);
lib/std/tests.rad +11 -8
110 110
    let dec = try fmt::parseInt("123") catch {
111 111
        throw testing::TestError::Failed;
112 112
    };
113 113
    try testing::expect(dec.magnitude == 123);
114 114
    try testing::expect(dec.radix == fmt::Radix::Decimal);
115 -
    try testing::expect(not dec.signed);
116 -
    try testing::expect(not dec.negative);
117 115
118 -
    let hex = try fmt::parseInt("-0x2a") catch {
116 +
    let hex = try fmt::parseInt("0x2a") catch {
119 117
        throw testing::TestError::Failed;
120 118
    };
121 119
    try testing::expect(hex.magnitude == 42);
122 120
    try testing::expect(hex.radix == fmt::Radix::Hex);
123 -
    try testing::expect(hex.signed);
124 -
    try testing::expect(hex.negative);
125 121
126 -
    let bin = try fmt::parseInt("+0b101") catch {
122 +
    let bin = try fmt::parseInt("0b101") catch {
127 123
        throw testing::TestError::Failed;
128 124
    };
129 125
    try testing::expect(bin.magnitude == 5);
130 126
    try testing::expect(bin.radix == fmt::Radix::Binary);
131 -
    try testing::expect(bin.signed);
132 -
    try testing::expect(not bin.negative);
133 127
}
134 128
135 129
@test fn testDigitFromAscii() throws (testing::TestError) {
136 130
    let zero = fmt::digitFromAscii('0', 10) else throw testing::TestError::Failed;
137 131
    try testing::expect(zero == 0);
160 154
        return;
161 155
    };
162 156
    throw testing::TestError::Failed;
163 157
}
164 158
159 +
@test fn testParseIntLiteralTextRejectsSigns() throws (testing::TestError) {
160 +
    try fmt::parseInt("-1") catch {
161 +
        try fmt::parseInt("+1") catch {
162 +
            return;
163 +
        };
164 +
    };
165 +
    throw testing::TestError::Failed;
166 +
}
167 +
165 168
@test fn testParseIntLiteralTextInvalidDigitErrors() throws (testing::TestError) {
166 169
    try fmt::parseInt("0b2") catch {
167 170
        return;
168 171
    };
169 172
    throw testing::TestError::Failed;
test/tests/arith.subword.ril +0 -1
242 242
  @assert.fail29
243 243
    unreachable;
244 244
  @assert.ok30
245 245
    ret 0;
246 246
}
247 -
test/tests/arith.w64.ril +0 -1
293 293
  @assert.fail35
294 294
    unreachable;
295 295
  @assert.ok36
296 296
    ret 0;
297 297
}
298 -
test/tests/cast.same.size.ril +0 -1
46 46
  @assert.fail19
47 47
    unreachable;
48 48
  @assert.ok20
49 49
    ret 0;
50 50
}
51 -
test/tests/match.nested.iflet.ril +0 -1
114 114
  @assert.fail13
115 115
    unreachable;
116 116
  @assert.ok14
117 117
    ret 0;
118 118
}
119 -
test/tests/match.nested.pattern.ril +0 -1
406 406
  @assert.fail37
407 407
    unreachable;
408 408
  @assert.ok38
409 409
    ret 0;
410 410
}
411 -
test/tests/match.nested.record.ril +0 -1
174 174
  @assert.fail17
175 175
    unreachable;
176 176
  @assert.ok18
177 177
    ret 0;
178 178
}
179 -
test/tests/match.nested.union.ril +0 -1
222 222
  @assert.fail23
223 223
    unreachable;
224 224
  @assert.ok24
225 225
    ret 0;
226 226
}
227 -
test/tests/unop.rad +10 -0
1 1
/// Returns the arithmetic negation of its argument.
2 2
fn neg(x: i32) -> i32 {
3 3
    return -x;
4 4
}
5 5
6 +
/// Subtracts a literal without requiring whitespace before the operator.
7 +
fn subtractAdjacent(value: i32) -> i32 {
8 +
    return value-2;
9 +
}
10 +
11 +
/// Returns a negated integer literal.
12 +
fn negLiteral() -> i32 {
13 +
    return -42;
14 +
}
15 +
6 16
/// Returns the arithmetic negation of an i8.
7 17
fn negI8(x: i8) -> i8 {
8 18
    return -x;
9 19
}
10 20
test/tests/unop.ril +11 -0
2 2
  @entry0
3 3
    neg w32 %1 %0;
4 4
    ret %1;
5 5
}
6 6
7 +
fn w32 $subtractAdjacent(w32 %0) {
8 +
  @entry0
9 +
    sub w32 %1 %0 2;
10 +
    ret %1;
11 +
}
12 +
13 +
fn w32 $negLiteral() {
14 +
  @entry0
15 +
    ret -42;
16 +
}
17 +
7 18
fn w8 $negI8(w8 %0) {
8 19
  @entry0
9 20
    neg w8 %1 %0;
10 21
    sext w8 %2 %1;
11 22
    ret %2;