*/
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`.
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.
*
checked = true;
+ if (null_cond_member_access) {
+ return check_cond_access (context);
+ }
+
if (inner != null) {
inner.check (context);
}
}
}
+ 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);
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;
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) {
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;
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 ();
case TokenType.DOT:
// pointer member access
case TokenType.OP_PTR:
+ // null-conditional member access
+ case TokenType.NULL_COND:
rollback (begin);
return true;
default:
type = TokenType.OP_COALESCING;
current++;
}
+ if (current < end && current[0] == '.') {
+ type = TokenType.NULL_COND;
+ current++;
+ }
break;
case '|':
type = TokenType.BITWISE_OR;
DO,
DOUBLE_COLON,
DOT,
+ NULL_COND,
DYNAMIC,
ELLIPSIS,
ELSE,
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'";