[vala/wip/null-conditional: 1/2] vala: Add conditional member access.
- From: Rico Tzschichholz <ricotz src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [vala/wip/null-conditional: 1/2] vala: Add conditional member access.
- Date: Thu, 19 Dec 2019 09:19:40 +0000 (UTC)
commit 1984952531043e6b713e2884d640d4b976a43c88
Author: Jeremy Philippe <jeremy philippe gmail com>
Date: Wed Dec 18 16:54:05 2019 +0100
vala: Add conditional member access.
This implements support for the "null-conditional" operator of C#
(also called "safe navigation" operator).
It supports both plain member access and method calls (including void
method calls, which are simply bypassed if the inner expression is
null).
vala/valamemberaccess.vala | 101 ++++++++++++++++++++++++++++++++++++++++++
vala/valamethodcall.vala | 108 +++++++++++++++++++++++++++++++++++++++++++++
vala/valaparser.vala | 18 ++++++++
vala/valascanner.vala | 4 ++
vala/valatokentype.vala | 2 +
5 files changed, 233 insertions(+)
---
diff --git a/vala/valamemberaccess.vala b/vala/valamemberaccess.vala
index 946bd2b0a..b284a09ac 100644
--- a/vala/valamemberaccess.vala
+++ b/vala/valamemberaccess.vala
@@ -51,6 +51,11 @@ public class Vala.MemberAccess : Expression {
*/
public bool pointer_member_access { get; set; }
+ /**
+ * Null-conditional member access.
+ */
+ public bool null_cond_member_access { get; set; }
+
/**
* Represents access to an instance member without an actual instance,
* e.g. `MyClass.an_instance_method`.
@@ -97,6 +102,13 @@ public class Vala.MemberAccess : Expression {
pointer_member_access = true;
}
+ public MemberAccess.null_cond (Expression inner, string member_name, SourceReference?
source_reference = null) {
+ this.inner = inner;
+ this.member_name = member_name;
+ this.source_reference = source_reference;
+ null_cond_member_access = true;
+ }
+
/**
* Appends the specified type as generic type argument.
*
@@ -207,6 +219,10 @@ public class Vala.MemberAccess : Expression {
checked = true;
+ if (null_cond_member_access) {
+ return check_cond_access (context);
+ }
+
if (inner != null) {
inner.check (context);
}
@@ -953,6 +969,91 @@ public class Vala.MemberAccess : Expression {
}
}
+ public bool check_cond_access (CodeContext context) {
+ if (inner == null) {
+ error = true;
+ Report.error (source_reference, "conditional member access without inner expression");
+ return false;
+ }
+
+ // Analyze the inner expression to get its value type
+ if (!inner.check (context)) {
+ error = true;
+ return false;
+ }
+
+ if (inner.value_type == null) {
+ error = true;
+ Report.error (inner.source_reference, "invalid type for inner expression");
+ return false;
+ }
+
+ // Declare the inner expression as a local variable to check for null
+ var inner_type = inner.value_type.copy ();
+ inner_type.value_owned = false;
+ if (context.experimental_non_null && !inner_type.nullable) {
+ Report.warning (inner.source_reference, "inner expression is never null");
+ inner_type.nullable = true;
+ }
+ var inner_local = new LocalVariable (inner_type, get_temp_name (), inner, source_reference);
+ var inner_decl = new DeclarationStatement (inner_local, source_reference);
+ insert_statement (context.analyzer.insert_block, inner_decl);
+
+ if (!inner_decl.check (context)) {
+ error = true;
+ return false;
+ }
+
+ // Create an equivalent, but non null-conditional, member access expression
+ Expression inner_access = new MemberAccess.simple (inner_local.name, source_reference);
+ if (context.experimental_non_null) {
+ inner_access = new CastExpression.non_null (new MemberAccess.simple
(inner_local.name, source_reference), source_reference);
+ }
+ var non_null_access = new MemberAccess (inner_access, member_name, source_reference);
+
+ if (!non_null_access.check (context)) {
+ error = true;
+ return false;
+ }
+
+ if (non_null_access.value_type == null) {
+ error = true;
+ Report.error (source_reference, "invalid type for member access expression");
+ return false;
+ }
+
+ // Declare a null local variable for the result
+ var result_type = non_null_access.value_type.copy ();
+ result_type.nullable = true;
+ var result_local = new LocalVariable (result_type, get_temp_name (), new NullLiteral
(source_reference), source_reference);
+ var result_decl = new DeclarationStatement (result_local, source_reference);
+ insert_statement (context.analyzer.insert_block, result_decl);
+
+ if (!result_decl.check (context)) {
+ error = true;
+ return false;
+ }
+
+ var non_null_cond = new BinaryExpression (BinaryOperator.INEQUALITY, new MemberAccess.simple
(inner_local.name, source_reference), new NullLiteral (source_reference), source_reference);
+ var non_null_assign = new ExpressionStatement (new Assignment (new MemberAccess.simple
(result_local.name, source_reference), non_null_access, AssignmentOperator.SIMPLE, source_reference),
source_reference);
+ var non_null_block = new Block (source_reference);
+ non_null_block.add_statement (non_null_assign);
+ var non_null_stmt = new IfStatement (non_null_cond, non_null_block, null, source_reference);
+ insert_statement (context.analyzer.insert_block, non_null_stmt);
+
+ if (!non_null_stmt.check (context)) {
+ error = true;
+ return false;
+ }
+
+ var result_access = new MemberAccess.simple (result_local.name, source_reference);
+ result_access.formal_target_type = formal_target_type;
+ result_access.target_type = target_type;
+
+ parent_node.replace_expression (this, result_access);
+ return result_access.check (context);
+ }
+
public override void emit (CodeGenerator codegen) {
if (inner != null) {
inner.emit (codegen);
diff --git a/vala/valamethodcall.vala b/vala/valamethodcall.vala
index 1c6e2b404..61e82a305 100644
--- a/vala/valamethodcall.vala
+++ b/vala/valamethodcall.vala
@@ -174,6 +174,11 @@ public class Vala.MethodCall : Expression {
checked = true;
+ var call_member_access = call as MemberAccess;
+ if (call_member_access != null && call_member_access.null_cond_member_access) {
+ return check_cond_access (context, call_member_access);
+ }
+
if (!call.check (context)) {
/* if method resolving didn't succeed, skip this check */
error = true;
@@ -684,6 +689,109 @@ public class Vala.MethodCall : Expression {
return !error;
}
+ private bool check_cond_access (CodeContext context, MemberAccess member_access) {
+ if (member_access.inner == null) {
+ error = true;
+ Report.error (source_reference, "conditional member access without inner expression");
+ return false;
+ }
+
+ // Analyze the inner expression to get its value type
+ if (!member_access.inner.check (context)) {
+ error = true;
+ return false;
+ }
+
+ if (member_access.inner.value_type == null) {
+ error = true;
+ Report.error (member_access.inner.source_reference, "invalid type for inner
expression");
+ return false;
+ }
+
+ // Declare the inner expression as a local variable to check for null
+ var inner_type = member_access.inner.value_type.copy ();
+ inner_type.value_owned = false;
+ if (context.experimental_non_null && !inner_type.nullable) {
+ Report.warning (member_access.inner.source_reference, "inner expression is never
null");
+ inner_type.nullable = true;
+ }
+ var inner_local = new LocalVariable (inner_type, get_temp_name (), member_access.inner,
source_reference);
+ var inner_decl = new DeclarationStatement (inner_local, source_reference);
+ insert_statement (context.analyzer.insert_block, inner_decl);
+
+ if (!inner_decl.check (context)) {
+ error = true;
+ return false;
+ }
+
+ // Create an equivalent, but non null-conditional, member access expression
+ Expression inner_access = new MemberAccess.simple (inner_local.name, source_reference);
+ if (context.experimental_non_null) {
+ inner_access = new CastExpression.non_null (new MemberAccess.simple
(inner_local.name, source_reference), source_reference);
+ }
+ var non_null_access = new MemberAccess (inner_access, member_access.member_name,
source_reference);
+
+ if (!non_null_access.check (context)) {
+ error = true;
+ return false;
+ }
+
+ if (non_null_access.value_type == null) {
+ error = true;
+ Report.error (source_reference, "invalid type for member access expression");
+ return false;
+ }
+
+ var result_type = non_null_access.value_type.get_return_type ().copy ();
+ if (result_type is VoidType) {
+ // For a void method call, replace the parent expression statement by a simple if
statement
+ var non_null_cond = new BinaryExpression (BinaryOperator.INEQUALITY, new
MemberAccess.simple (inner_local.name, source_reference), new NullLiteral (source_reference),
source_reference);
+ var non_null_call = new ExpressionStatement (new MethodCall(non_null_access,
source_reference), source_reference);
+ var non_null_block = new Block (source_reference);
+ non_null_block.add_statement (non_null_call);
+ var non_null_stmt = new IfStatement (non_null_cond, non_null_block, null,
source_reference);
+
+ if (!(parent_node is Statement)) {
+ error = true;
+ Report.error (source_reference, "void method call without a parent
statement");
+ return false;
+ }
+
+ ((Block) parent_node.parent_node).replace_statement ((Statement) parent_node,
non_null_stmt);
+ return non_null_stmt.check (context);
+ } else {
+ // Otherwise, if the method has a non-void return type, declare a null local variable
for the result
+ result_type.nullable = true;
+ var result_local = new LocalVariable (result_type, get_temp_name (), new NullLiteral
(source_reference), source_reference);
+ var result_decl = new DeclarationStatement (result_local, source_reference);
+ insert_statement (context.analyzer.insert_block, result_decl);
+
+ if (!result_decl.check (context)) {
+ error = true;
+ return false;
+ }
+
+ var non_null_cond = new BinaryExpression (BinaryOperator.INEQUALITY, new
MemberAccess.simple (inner_local.name, source_reference), new NullLiteral (source_reference),
source_reference);
+ var non_null_assign = new ExpressionStatement (new Assignment (new
MemberAccess.simple (result_local.name, source_reference), new MethodCall(non_null_access, source_reference),
AssignmentOperator.SIMPLE, source_reference), source_reference);
+ var non_null_block = new Block (source_reference);
+ non_null_block.add_statement (non_null_assign);
+ var non_null_stmt = new IfStatement (non_null_cond, non_null_block, null,
source_reference);
+ insert_statement (context.analyzer.insert_block, non_null_stmt);
+
+ if (!non_null_stmt.check (context)) {
+ error = true;
+ return false;
+ }
+
+ var result_access = new MemberAccess.simple (result_local.name, source_reference);
+ result_access.formal_target_type = formal_target_type;
+ result_access.target_type = target_type;
+
+ parent_node.replace_expression (this, result_access);
+ return result_access.check (context);
+ }
+ }
+
public override void emit (CodeGenerator codegen) {
unowned MethodType? method_type = call.value_type as MethodType;
if (method_type != null && method_type.method_symbol.parent_symbol is Signal) {
diff --git a/vala/valaparser.vala b/vala/valaparser.vala
index 45a420955..915a282a7 100644
--- a/vala/valaparser.vala
+++ b/vala/valaparser.vala
@@ -659,6 +659,9 @@ public class Vala.Parser : CodeVisitor {
case TokenType.OP_PTR:
expr = parse_pointer_member_access (begin, expr);
break;
+ case TokenType.NULL_COND:
+ expr = parse_null_cond_member_access (begin, expr);
+ break;
case TokenType.OPEN_PARENS:
expr = parse_method_call (begin, expr);
break;
@@ -769,6 +772,19 @@ public class Vala.Parser : CodeVisitor {
return expr;
}
+ Expression parse_null_cond_member_access (SourceLocation begin, Expression inner) throws ParseError {
+ expect (TokenType.NULL_COND);
+ string id = parse_identifier ();
+ List<DataType> type_arg_list = parse_type_argument_list (true);
+ var expr = new MemberAccess.null_cond (inner, id, get_src (begin));
+ if (type_arg_list != null) {
+ foreach (DataType type_arg in type_arg_list) {
+ expr.add_type_argument (type_arg);
+ }
+ }
+ return expr;
+ }
+
Expression parse_method_call (SourceLocation begin, Expression inner) throws ParseError {
expect (TokenType.OPEN_PARENS);
var arg_list = parse_argument_list ();
@@ -1645,6 +1661,8 @@ public class Vala.Parser : CodeVisitor {
case TokenType.DOT:
// pointer member access
case TokenType.OP_PTR:
+ // null-conditional member access
+ case TokenType.NULL_COND:
rollback (begin);
return true;
default:
diff --git a/vala/valascanner.vala b/vala/valascanner.vala
index a2e76b4d5..23f60e5d3 100644
--- a/vala/valascanner.vala
+++ b/vala/valascanner.vala
@@ -940,6 +940,10 @@ public class Vala.Scanner {
type = TokenType.OP_COALESCING;
current++;
}
+ if (current < end && current[0] == '.') {
+ type = TokenType.NULL_COND;
+ current++;
+ }
break;
case '|':
type = TokenType.BITWISE_OR;
diff --git a/vala/valatokentype.vala b/vala/valatokentype.vala
index 75cf92e6c..a9599c136 100644
--- a/vala/valatokentype.vala
+++ b/vala/valatokentype.vala
@@ -63,6 +63,7 @@ public enum Vala.TokenType {
DO,
DOUBLE_COLON,
DOT,
+ NULL_COND,
DYNAMIC,
ELLIPSIS,
ELSE,
@@ -196,6 +197,7 @@ public enum Vala.TokenType {
case DO: return "`do'";
case DOUBLE_COLON: return "`::'";
case DOT: return "`.'";
+ case NULL_COND: return "`?.'";
case DYNAMIC: return "`dynamic'";
case ELLIPSIS: return "`...'";
case ELSE: return "`else'";
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]