Skip to content

Commit

Permalink
nullableObject? no longer returns null. Instead, it breakelses null.
Browse files Browse the repository at this point in the history
Generally, null is treated equivalently to :else in breakelse features.
This allows nullableObject?.else(...) and generally nullableObject? in if expressions.
Also, .else now creates a breakelse targetable scope on its left.
  • Loading branch information
FeepingCreature committed Oct 9, 2023
1 parent b501817 commit c355e74
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 46 deletions.
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fi

FLAGS="$JFLAG -I$($LLVM_CONFIG --includedir) -L-L$($LLVM_CONFIG --libdir)"

TAG=v0.3.1
TAG=v0.4.0
NEAT=.cache/bootstrap/"$TAG"/neat-"$TAG"-gcc/neat

if [ ! -f "$NEAT" ]
Expand Down
4 changes: 3 additions & 1 deletion src/neat/base.nt
Original file line number Diff line number Diff line change
Expand Up @@ -1815,5 +1815,7 @@ void printAtErrorLoc(LocRange locRange, nullable FileIdTable fileIdTable, string
}

string getLine(string text, int line) {
return text.split("\n")[line];
auto lines = text.split("\n");
if (line >= lines.length) return "???????????????";
return lines[line];
}
2 changes: 1 addition & 1 deletion src/neat/class_.nt
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,7 @@ bool isStrictSubtypeOf(Type first, Type second)
// FIXME __init()
auto astExpr = context.compiler.$expr ({
auto base = $astBase;
mut uninitialized ($astNnType | fail nullptr_t) result;
mut uninitialized ($astNnType | nullptr_t) result;
if (auto base = base) result = base;
else result = null;
result;
Expand Down
127 changes: 127 additions & 0 deletions src/neat/elseexpr.nt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
module neat.elseexpr;

macro import std.macro.listcomprehension;
macro import std.macro.quasiquoting;

import neat.base;
import neat.bottom;
import neat.either;
import neat.expr;
import neat.statements;
import neat.util;

/**
* Is this type a type that should trigger a breakelse?
* - :else
* - nullptr_t
*/
bool isElseTriggerType(Type type) {
if (type.instanceOf(SymbolIdentifierType).case(null: breakelse).name == "else") {
return true;
}
if (type.instanceOf(NullPointer)) return true;
return false;
}

class ASTElseExpr : ASTSymbol
{
ASTSymbol base;

ASTSymbol else_;

this(this.base, this.else_, super) { }

override (Symbol | fail Error) compile(Context context) {
/**
* - make label
* - LabelIfScope
* - preallocate var
* if (true) {
* var = base.case(:else: breakelse)
* } else {
* var = else_
* }
* - var
*/
auto ifLabel = context.getLabel;
auto context = context.withNamespace(new LabelIfScope(ifLabel, hasElse=true, context.namespace));
auto base = this.base.compile(context)?.beExpressionImplCall(context, base.locRange)?;
auto else_ = this.else_.compile(context)?.beExpressionImplCall(context, else_.locRange)?;
// won't be visible post-merge
bool elseIsBottom = !!else_.type.instanceOf(Bottom);
(Expression | fail Error) baseEither() {
if (auto either = base.type.instanceOf(Either)) {
return base;
}
if (auto canTreatAsEither = base.type.instanceOf(CanTreatAsEither)) {
return canTreatAsEither.toEitherType(context, base)?.case(null: breakelse);
}
return this.locRange.fail(
"'else' operator cannot be applied to non-sumtype $(base.type.repr)");
}
auto baseEither = baseEither?;
auto baseEitherType = baseEither.type.instanceOf(Either).notNull;
if (![any a.type.isElseTriggerType for a in baseEitherType.types]) {
return this.locRange.fail(
"'else' operator cannot be applied to sumtype without :else member");
}
auto merger = new TypeMerger;
mut nullable Type triggerType;
for (auto entry in baseEitherType.types) {
if (entry.type.isElseTriggerType) {
this.locRange.assert(!triggerType, "internal error: double trigger type")?;
triggerType = entry.type;
} else {
merger.add(new NullExpr(entry.type), __RANGE__, context)?;
}
}
merger.add(else_, __RANGE__, context)?;
auto mergedType = merger.type(context).notNull;
print("merged to $(mergedType) from $(baseEitherType.types) and $(else_.type)");
/**
* uninitialized mergedType result;
* if (true) { result = baseEither.case(:else: breakelse); }
* else { result = else_; }
*/
auto astBaseEither = new ASTSymbolHelper(baseEither);
auto astTriggerType = new ASTSymbolHelper(triggerType);
auto astBaseBreakElse = context.compiler.$expr $astBaseEither.case($astTriggerType: breakelse);
auto baseBreakElse = astBaseBreakElse.compile(context)?.beExpression(__RANGE__)?;
auto lifetimeBaseBreakElse = baseBreakElse if elseIsBottom else baseBreakElse.take(context, __RANGE__)?;
auto baseBreakElseConvert = expectImplicitConvertTo(
context, lifetimeBaseBreakElse, mergedType, this.locRange)?;
auto elseConvert = expectImplicitConvertTo(context, else_, mergedType, this.locRange)?;
// If else is bottom, we can just use the lifetime of base directly.
auto lifetime = baseEither.info.lifetime if elseIsBottom else Lifetime.gifted;
auto result = new PairedTemporary(mergedType, lifetime, context.getUniqueId);
auto init = new UninitializeTemporaryStatement(result);
auto ifTrue = new AssignStatement(result, baseBreakElseConvert, __RANGE__);
auto ifFalse = new AssignStatement(result, elseConvert, __RANGE__);
auto trueTest = new BoolLiteral(true);
auto ifStmt = new IfStatement(ifLabel, trueTest, then=ifTrue, else_=ifFalse, __RANGE__);
return context.compiler.(wrap(sequence(init, ifStmt), result, null));
}

override string repr() => "($(base)).else($else_)";
}

// expr.else(expr)
(nullable ASTSymbol | fail Error) parseElseExpr(Parser parser, LexicalContext lexicalContext, ASTSymbol current) {
parser.begin;
auto from = parser.from;
if (!(parser.acceptToken(TokenType.dot)
&& parser.acceptIdentifier("else")
&& parser.acceptToken(TokenType.lparen)))
{
parser.revert;
return null;
}
parser.commit;

auto elseExpr = lexicalContext.compiler.parseExpression(parser, lexicalContext)?;
auto range = parser.to(from);
range.assert(!!elseExpr, () => "expression expected")?;
auto elseExpr = elseExpr.notNull;
parser.expectToken(TokenType.rparen)?;
return new ASTElseExpr(base=current, else_=elseExpr, range);
}
53 changes: 11 additions & 42 deletions src/neat/stuff.nt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import neat.bottom;
import neat.class_;
import neat.decl;
import neat.delegate_;
import neat.elseexpr;
import neat.either;
import neat.enums;
import neat.expr;
Expand Down Expand Up @@ -135,8 +136,6 @@ class DefineClassIntf : FinishedSymbol
* mut T mut[] foo;
* foo ~= T(); // mut T[] - the array value is mutable
* foo[0] = T(); // T mut[] - the array elements are mutable
*
* TODO: actually implement this
*/
bool arrayIsMutable = parser.acceptIdentifier("mut");
if (parser.acceptToken(TokenType.lsquarebracket)) {
Expand Down Expand Up @@ -732,9 +731,8 @@ class WrapReference : Reference
if (autoCall) return (:mismatch, locRange.fail("cannot call"));
nullable Expression expr = target.instanceOf(Expression);
// TODO
// auto funcptr = expr?.type.instanceOf(FunctionPointer);
mut nullable FunctionPointer funcptr;
if (expr) funcptr = expr.type.instanceOf(FunctionPointer);
// auto funcptr = expr?.type.instanceOf(FunctionPointer).else(null);
auto funcptr = expr.case(Expression e: e.type.instanceOf(FunctionPointer));
if (expr && funcptr) {
auto result = validateCall(context, funcptr.params, astArgs, "", variadic=false, locRange)?.case(
(:argCastError, Error err): return (:argCastError, err),
Expand Down Expand Up @@ -1247,18 +1245,14 @@ class ASTPropagateFailureExpr : ASTSymbol
for (k, v in either.types) {
ASTSymbol expr() {
auto a = new ASTIdentifier("a", false, __RANGE__);
// Aaaaaaaugh!
// if (v.type.instanceOf(SymbolIdentifierType).case(null: breakelse).name == "else") {
if (auto asIdentifierType = v.type.instanceOf(SymbolIdentifierType)) {
if (asIdentifierType.name == "else") {
return new ASTBreakElse(__RANGE__);
}
if (v.type.isElseTriggerType) {
return new ASTBreakElse(this.locRange);
}
if (v.fail) return new ASTReturn(a, __RANGE__);
if (v.fail) return new ASTReturn(a, this.locRange);
return a;
}
cases ~= ASTEitherCaseExprCase(
__RANGE__,
this.locRange,
new ASTSymbolHelper(v.type),
"a",
fail=v.fail,
Expand All @@ -1285,26 +1279,6 @@ class ASTPropagateFailureExpr : ASTSymbol
return new ASTPropagateFailureExpr(left, parser.to(from));
}

// expr.else(expr)
(nullable ASTSymbol | fail Error) parseElseExpr(Parser parser, LexicalContext lexicalContext, ASTSymbol current) {
parser.begin;
if (!(parser.acceptToken(TokenType.dot)
&& parser.acceptIdentifier("else")
&& parser.acceptToken(TokenType.lparen)))
{
parser.revert;
return null;
}
auto from = parser.from;
parser.commit;

auto elseExpr = lexicalContext.compiler.parseExpression(parser, lexicalContext)?;
parser.to(from).assert(!!elseExpr, () => "expression expected")?;
auto elseExpr = elseExpr.notNull;
parser.expectToken(TokenType.rparen)?;
return lexicalContext.compiler.$expr $current.case(:else: $elseExpr);
}

(nullable ASTSymbol | fail Error) parseProperties(ParserImpl parser, LexicalContext lexicalContext)
{
mut (:inc | :dec | :none) preincdec = :none, postincdec = :none;
Expand All @@ -1313,14 +1287,9 @@ class ASTPropagateFailureExpr : ASTSymbol
else if (parser.acceptToken2(TokenType.plus, TokenType.plus)) preincdec = :inc;
auto toPre = parser.to(fromPre);

/**
* TODO
* mut ASTSymbol current = parseExpressionBase(parser, lexicalContext)?.case(null: return null);
*/
mut uninitialized ASTSymbol current;
if (auto base = parseExpressionBase(parser, lexicalContext)?)
current = base;
else return null;
// TODO
// mut ASTSymbol current = parseExpressionBase(parser, lexicalContext)??.else(return null);
mut ASTSymbol current = parseExpressionBase(parser, lexicalContext)?.case(null: return null);

while (true) {
if (ASTSymbol instanceOf = parser.parseInstanceOf(lexicalContext, current)?) {
Expand Down Expand Up @@ -2369,7 +2338,7 @@ class ASTIfStatement : ASTStatement
ASTVarDeclStatement vardecl: {
auto initial = vardecl.initial.compile(context)?.beExpressionImplCall(context, vardecl.locRange)?;
// TODO
// auto declType = vardecl.type.compile(context)?.beType(vardecl.type.locRange)? if vardecl.type else null;
// auto declType = vardecl.type?.compile(context)?.beType(vardecl.type.locRange).else(null);
mut nullable Type declType;
if (vardecl.type)
declType = vardecl.type.compile(context)?.beType(vardecl.type.locRange)?;
Expand Down
2 changes: 2 additions & 0 deletions src/std/macro/listcomprehension.nt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class ASTListComprehension : ASTSymbol
mut auto type = astType.compile(context)?;

bool defaultIsNullExpr() {
// TODO
// return default_?.else(return false)
return default_.case(null: return false)
.(compiler.destructAstIdentifier(that))
.case(string name: name == "null", :none: false);
Expand Down
15 changes: 14 additions & 1 deletion test/runnable/eithertest.nt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ class Bar {
this(this.foo) {}
}

class Baz {
nullable Foo foo;
this(this.foo) {}
}

void testObjHack() {
bool test(nullable Foo nullableFoo) {
Foo foo = nullableFoo.case(null: return false);
Expand All @@ -101,7 +106,7 @@ void testObjHack() {
assert(test(new Foo));

nullable Foo test2(nullable Bar bar) {
Foo foo = bar?.foo;
Foo foo = bar.case(null: return null).foo;
return foo;
}
assert(!test2(null));
Expand All @@ -114,4 +119,12 @@ void testObjHack() {
}
assert(!test3(null));
assert(test3(new Foo));

void test4(nullable Baz nullableBaz, (Foo | bool) outcome) {
assert(nullableBaz?.foo.else(false) == outcome);
}
auto foo = new Foo;
test4(null, false);
test4(new Baz(null), false);
test4(new Baz(foo), foo);
}
28 changes: 28 additions & 0 deletions test/runnable/lifetime.nt
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,33 @@ void test_either_replace()
assert(trace == "-S0 -T0 -U0");
}

void test_else_prop()
{
dbg("- else property");
{
string trace;
{
nullable C c = new C(S("S", &trace, 0));
auto value = c.else(false);
assert(trace == "+S1 -S0");
}
assert(trace == "+S1 -S0 -S1");
}
{
string trace;
(int | :else) test = :else;
test.else(new C(S("S", &trace, 0)));
assert(trace == "+S1 -S0 -S1");
}
{
string trace;
(int | :else) test = 0;
test.else(new C(S("S", &trace, 0)));
// else block is never evaluated
assert(trace == "");
}
}

class ErrorPropTest {
(void | fail S) field;
this() { }
Expand Down Expand Up @@ -1162,6 +1189,7 @@ void main()
test_class_member_ref;
test_either_member;
test_either_replace;
test_else_prop;
test_error_prop;
test_array_cat;
test_statement_expr;
Expand Down

0 comments on commit c355e74

Please sign in to comment.