From: Nick Schrader Date: Fri, 27 Mar 2020 16:12:09 +0000 (-0300) Subject: Add support for "with" statement X-Git-Tag: 0.49.1~114 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=35f72f3ecdf0d61d0e67cd4f72c29fee2c3c9a55;p=thirdparty%2Fvala.git Add support for "with" statement Creates data type scoped blocks which allow implicit member access to the given expression or declaration statement. with (expr) { ...; } Within the with-block the expression's members can be directly accessed without the member access operator. Members may hide local, class and instance varibales with the same name. Instance variables are still accessible through this. A local variable can be directly declared in the with statement header. Hidden local and class variables are currently not directly accessible (using this for class members generates the expected warning). Fixes https://gitlab.gnome.org/GNOME/vala/issues/327 --- diff --git a/tests/Makefile.am b/tests/Makefile.am index 263e8975e..24e8964fa 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -505,6 +505,9 @@ TESTS = \ objects/bug795225-3.test \ objects/bug795225-4.test \ objects/bug795521.vala \ + objects/with-expression.vala \ + objects/with-instance.vala \ + objects/with-nested.vala \ errors/catch-error-code.vala \ errors/catch-in-finally.vala \ errors/default-gtype.vala \ @@ -734,6 +737,12 @@ TESTS = \ parser/using-ambiguous-reference.test \ parser/using-directive.vala \ parser/using-invalid-namespace.test \ + parser/with-embedded.vala \ + parser/with-empty.vala \ + parser/with-invalid-declaration.test \ + parser/with-invalid-expression.test \ + parser/with-no-block.test \ + parser/with-no-expression.test \ parser/yield-method.test \ parser/yield-return.test \ parser/yield-return.vala \ @@ -928,6 +937,26 @@ TESTS = \ semantic/unary-unsupported-increment.test \ semantic/unary-unsupported-minus.test \ semantic/unary-unsupported-negation.test \ + semantic/with-array.test \ + semantic/with-buildin.vala \ + semantic/with-class.test \ + semantic/with-compact.vala \ + semantic/with-declaration-cast-type.vala \ + semantic/with-declaration-wrong-type.test \ + semantic/with-declaration.vala \ + semantic/with-dereferenced-pointer.vala \ + semantic/with-enum.test \ + semantic/with-enum-member.vala \ + semantic/with-error.test \ + semantic/with-error-member.test \ + semantic/with-pointer.test \ + semantic/with-namespace.test \ + semantic/with-no-declaration.test \ + semantic/with-no-such-member.test \ + semantic/with-null.vala \ + semantic/with-string.vala \ + semantic/with-outside-declaration.test \ + semantic/with-value.vala \ semantic/yield-call-requires-async-context.test \ semantic/yield-call-requires-async-method.test \ semantic/yield-creation-requires-async-context.test \ @@ -941,6 +970,7 @@ NON_NULL_TESTS = \ nullability/member-access-nullable-instance.test \ nullability/method-parameter-invalid-convert.test \ nullability/method-return-invalid-convert.test \ + nullability/with-non-null.test \ $(NULL) LINUX_TESTS = \ diff --git a/tests/nullability/with-non-null.test b/tests/nullability/with-non-null.test new file mode 100644 index 000000000..ed8fb40df --- /dev/null +++ b/tests/nullability/with-non-null.test @@ -0,0 +1,12 @@ +Invalid Code + +class Foo { + public int i; +} + +void main () { + Foo? f = new Foo (); + with (f) { + i = 7; + } +} diff --git a/tests/objects/with-expression.vala b/tests/objects/with-expression.vala new file mode 100644 index 000000000..8e2821ded --- /dev/null +++ b/tests/objects/with-expression.vala @@ -0,0 +1,38 @@ +class Foo { + public static int factory_called = 0; + public static Foo factory () { + factory_called++; + return new Foo (); + } + + public static int method_called = 0; + public void method () { + method_called++; + } +} + +void test () { + var foo = new Foo (); + with (foo) + method (); + + with (new Foo ()) + method (); + + with (Foo.factory ()) { + method (); + method (); + } + + Foo[] arr = {foo, foo}; + with (arr[0]) { + method (); + } + + assert (Foo.method_called == 5); + assert (Foo.factory_called == 1); +} + +void main () { + test (); +} diff --git a/tests/objects/with-instance.vala b/tests/objects/with-instance.vala new file mode 100644 index 000000000..715833ef8 --- /dev/null +++ b/tests/objects/with-instance.vala @@ -0,0 +1,73 @@ +class Foo { + public int field; + public string prop { get; set; } + + public bool method_called = false; + public void method () { + method_called = true; + } +} + +class Bar : Foo { } + +class TestFoo { + public static int class_field; + public int instance_field; + + public void test () { + var foo = new Foo (); + var local_field = 0; + + with (foo) { + field = 10; + prop = "prop"; + method (); + + local_field = 20; + class_field = 30; + instance_field = 40; + } + + assert (foo.field == 10); + assert (foo.prop == "prop"); + assert (foo.method_called); + + assert (local_field == 20); + assert (class_field == 30); + assert (instance_field == 40); + } +} + +// Copy and paste TestFoo, change Foo to Bar +class TestBar { + public static int class_field; + public int instance_field; + + public void test () { + var foo = new Bar (); + var local_field = 0; + + with (foo) { + field = 10; + prop = "prop"; + method (); + + local_field = 20; + class_field = 30; + instance_field = 40; + } + + assert (foo.field == 10); + assert (foo.prop == "prop"); + assert (foo.method_called); + + assert (local_field == 20); + assert (class_field == 30); + assert (instance_field == 40); + } +} + +void main () { + new TestFoo ().test (); + new TestBar ().test (); +} diff --git a/tests/objects/with-nested.vala b/tests/objects/with-nested.vala new file mode 100644 index 000000000..ac356654e --- /dev/null +++ b/tests/objects/with-nested.vala @@ -0,0 +1,34 @@ +class Foo { + public int field; +} + +class Bar { + public int field; +} + +class Test { + public int field; + + void nested () { + var foo = new Foo (); + var bar = new Bar (); + + with (var f = foo) { + field = 100; + with (bar) { + field = 200; + f.field = 300; + this.field = 400; + } + } + + assert (foo.field == 300); + assert (bar.field == 200); + assert (this.field == 400); + } + + static int main () { + new Test ().nested (); + return 0; + } +} diff --git a/tests/parser/with-embedded.vala b/tests/parser/with-embedded.vala new file mode 100644 index 000000000..c668e5afc --- /dev/null +++ b/tests/parser/with-embedded.vala @@ -0,0 +1,4 @@ +void main () { + with (10) + assert (to_string () == "10"); +} diff --git a/tests/parser/with-empty.vala b/tests/parser/with-empty.vala new file mode 100644 index 000000000..1a870b61f --- /dev/null +++ b/tests/parser/with-empty.vala @@ -0,0 +1,3 @@ +void main () { + with (10); +} diff --git a/tests/parser/with-invalid-declaration.test b/tests/parser/with-invalid-declaration.test new file mode 100644 index 000000000..ae1e775b2 --- /dev/null +++ b/tests/parser/with-invalid-declaration.test @@ -0,0 +1,6 @@ +Invalid Code + +void main () { + with (var f foo) { + } +} diff --git a/tests/parser/with-invalid-expression.test b/tests/parser/with-invalid-expression.test new file mode 100644 index 000000000..50fbe77e3 --- /dev/null +++ b/tests/parser/with-invalid-expression.test @@ -0,0 +1,6 @@ +Invalid Code + +void main () { + with (f foo) { + } +} diff --git a/tests/parser/with-no-block.test b/tests/parser/with-no-block.test new file mode 100644 index 000000000..237bd89d8 --- /dev/null +++ b/tests/parser/with-no-block.test @@ -0,0 +1,5 @@ +Invalid Code + +void main () { + with (10) +} \ No newline at end of file diff --git a/tests/parser/with-no-expression.test b/tests/parser/with-no-expression.test new file mode 100644 index 000000000..0e01d28ab --- /dev/null +++ b/tests/parser/with-no-expression.test @@ -0,0 +1,6 @@ +Invalid Code + +void main () { + with () { + } +} diff --git a/tests/semantic/with-array.test b/tests/semantic/with-array.test new file mode 100644 index 000000000..bf0b4d18d --- /dev/null +++ b/tests/semantic/with-array.test @@ -0,0 +1,7 @@ +Invalid Code + +void main () { + int[] arr = { 1, 2, 3 }; + with (arr) { + } +} diff --git a/tests/semantic/with-buildin.vala b/tests/semantic/with-buildin.vala new file mode 100644 index 000000000..999554adf --- /dev/null +++ b/tests/semantic/with-buildin.vala @@ -0,0 +1,5 @@ +void main () { + with (stdout) { + assert (!eof ()); + } +} diff --git a/tests/semantic/with-class.test b/tests/semantic/with-class.test new file mode 100644 index 000000000..2bea6e52c --- /dev/null +++ b/tests/semantic/with-class.test @@ -0,0 +1,9 @@ +Invalid Code + +class Foo { +} + +void main () { + with (Foo) { + } +} diff --git a/tests/semantic/with-compact.vala b/tests/semantic/with-compact.vala new file mode 100644 index 000000000..b2530c91f --- /dev/null +++ b/tests/semantic/with-compact.vala @@ -0,0 +1,13 @@ +[Compact] +class Foo { + public int i; +} + +void main () { + var foo = new Foo (); + with (foo) { + i = 13; + } + + assert (foo.i == 13); +} diff --git a/tests/semantic/with-declaration-cast-type.vala b/tests/semantic/with-declaration-cast-type.vala new file mode 100644 index 000000000..c855d1c0b --- /dev/null +++ b/tests/semantic/with-declaration-cast-type.vala @@ -0,0 +1,10 @@ +class Foo { +} + +class Bar : Foo { +} + +void main () { + with (Foo f = new Bar ()) { + } +} diff --git a/tests/semantic/with-declaration-wrong-type.test b/tests/semantic/with-declaration-wrong-type.test new file mode 100644 index 000000000..79ae8f8fb --- /dev/null +++ b/tests/semantic/with-declaration-wrong-type.test @@ -0,0 +1,12 @@ +Invalid Code + +class Foo { +} + +class Bar { +} + +void main () { + with (Bar f = new Foo ()) { + } +} diff --git a/tests/semantic/with-declaration.vala b/tests/semantic/with-declaration.vala new file mode 100644 index 000000000..78763c333 --- /dev/null +++ b/tests/semantic/with-declaration.vala @@ -0,0 +1,35 @@ +class Foo { + public int i; + public int j; +} + +void main () { + var foo = new Foo (); + with (foo) { + i = 10; + } + + assert (foo.i == 10); + with (var f = foo) { + i = 100; + f.j = 200; + } + + assert (foo.i == 100); + assert (foo.j == 200); + with (Foo f = foo) { + i = 1000; + f.j = 2000; + } + + assert (foo.i == 1000); + assert (foo.j == 2000); + Foo f; + with (f = foo) { + i = 10000; + f.j = 20000; + } + + assert (f.i == 10000); + assert (foo.j == 20000); +} diff --git a/tests/semantic/with-dereferenced-pointer.vala b/tests/semantic/with-dereferenced-pointer.vala new file mode 100644 index 000000000..42fc20fd8 --- /dev/null +++ b/tests/semantic/with-dereferenced-pointer.vala @@ -0,0 +1,14 @@ +class Foo { + public int i; +} + +void main () { + var f = new Foo (); + var p = &f; + + with (*p) { + i = 13; + } + + assert (f.i == 13); +} diff --git a/tests/semantic/with-enum-member.vala b/tests/semantic/with-enum-member.vala new file mode 100644 index 000000000..3b5517db0 --- /dev/null +++ b/tests/semantic/with-enum-member.vala @@ -0,0 +1,8 @@ +enum FooEnum { + FIRST +} + +void main () { + with (FooEnum.FIRST) { + } +} diff --git a/tests/semantic/with-enum.test b/tests/semantic/with-enum.test new file mode 100644 index 000000000..3a473a2f1 --- /dev/null +++ b/tests/semantic/with-enum.test @@ -0,0 +1,10 @@ +Invalid Code + +enum FooEnum { + FIRST +} + +void main () { + with (FooEnum) { + } +} diff --git a/tests/semantic/with-error-member.test b/tests/semantic/with-error-member.test new file mode 100644 index 000000000..6d883b6e5 --- /dev/null +++ b/tests/semantic/with-error-member.test @@ -0,0 +1,11 @@ +Invalid Code + +errordomain FooError { + FAIL +} + +void main () { + var e = new FooError.FAIL ("foo"); + with (e) { + } +} diff --git a/tests/semantic/with-error.test b/tests/semantic/with-error.test new file mode 100644 index 000000000..61cc28efa --- /dev/null +++ b/tests/semantic/with-error.test @@ -0,0 +1,10 @@ +Invalid Code + +errordomain FooError { + FAIL +} + +void main () { + with (FooError) { + } +} diff --git a/tests/semantic/with-namespace.test b/tests/semantic/with-namespace.test new file mode 100644 index 000000000..e328fe205 --- /dev/null +++ b/tests/semantic/with-namespace.test @@ -0,0 +1,9 @@ +Invalid Code + +namespace Foo { +} + +void main () { + with (Foo) { + } +} diff --git a/tests/semantic/with-no-declaration.test b/tests/semantic/with-no-declaration.test new file mode 100644 index 000000000..3b033a8e3 --- /dev/null +++ b/tests/semantic/with-no-declaration.test @@ -0,0 +1,9 @@ +Invalid Code + +class Foo { +} + +void main () { + with (f = new Foo ()) { + } +} diff --git a/tests/semantic/with-no-such-member.test b/tests/semantic/with-no-such-member.test new file mode 100644 index 000000000..5f20d21c8 --- /dev/null +++ b/tests/semantic/with-no-such-member.test @@ -0,0 +1,10 @@ +Invalid Code + +class Foo { +} + +void main () { + with (new Foo ()) { + field = 100; + } +} diff --git a/tests/semantic/with-null.vala b/tests/semantic/with-null.vala new file mode 100644 index 000000000..dd108b256 --- /dev/null +++ b/tests/semantic/with-null.vala @@ -0,0 +1,12 @@ +class Foo { + public int i; +} + +void main () { + Foo? f = null; + Process.exit (0); + + with (f) { + i = 7; + } +} diff --git a/tests/semantic/with-outside-declaration.test b/tests/semantic/with-outside-declaration.test new file mode 100644 index 000000000..86af413e7 --- /dev/null +++ b/tests/semantic/with-outside-declaration.test @@ -0,0 +1,13 @@ +Invalid Code + +class Foo { + public int i; +} + +void main () { + with (var f = new Foo ()) { + i = 10; + } + + f.i = 100; +} diff --git a/tests/semantic/with-pointer.test b/tests/semantic/with-pointer.test new file mode 100644 index 000000000..d268b1cd2 --- /dev/null +++ b/tests/semantic/with-pointer.test @@ -0,0 +1,7 @@ +Invalid Code + +void main () { + int i = 0; + with (&i) { + } +} diff --git a/tests/semantic/with-string.vala b/tests/semantic/with-string.vala new file mode 100644 index 000000000..133be17b6 --- /dev/null +++ b/tests/semantic/with-string.vala @@ -0,0 +1,5 @@ +void main () { + with ("string ") { + assert (chomp () == "string"); + } +} diff --git a/tests/semantic/with-value.vala b/tests/semantic/with-value.vala new file mode 100644 index 000000000..a0f111ad8 --- /dev/null +++ b/tests/semantic/with-value.vala @@ -0,0 +1,10 @@ +void main () { + int i = 0; + string s; + + with (i) { + s = i.to_string (); + } + + assert (s == "0"); +} diff --git a/vala/Makefile.am b/vala/Makefile.am index c833939a3..0191fc9aa 100644 --- a/vala/Makefile.am +++ b/vala/Makefile.am @@ -183,6 +183,7 @@ libvala_la_VALASOURCES = \ valaversionattribute.vala \ valavoidtype.vala \ valawhilestatement.vala \ + valawithstatement.vala \ valayieldstatement.vala \ $(NULL) diff --git a/vala/valacodevisitor.vala b/vala/valacodevisitor.vala index d961f85e9..a18e0e7fd 100644 --- a/vala/valacodevisitor.vala +++ b/vala/valacodevisitor.vala @@ -404,6 +404,14 @@ public abstract class Vala.CodeVisitor { public virtual void visit_unlock_statement (UnlockStatement stmt) { } + /** + * Visit operation called for with statements. + * + * @param stmt a with statement + */ + public virtual void visit_with_statement (WithStatement stmt) { + } + /** * Visit operation called for delete statements. * diff --git a/vala/valacodewriter.vala b/vala/valacodewriter.vala index 28ad9fd34..9658cdb1f 100644 --- a/vala/valacodewriter.vala +++ b/vala/valacodewriter.vala @@ -1161,6 +1161,16 @@ public class Vala.CodeWriter : CodeVisitor { write_newline (); } + public override void visit_with_statement (WithStatement stmt) { + write_indent (); + write_string ("with ("); + stmt.expression.accept (this); + write_string (")"); + stmt.body.accept (this); + write_string (";"); + write_newline (); + } + public override void visit_yield_statement (YieldStatement y) { write_indent (); write_string ("yield"); diff --git a/vala/valaflowanalyzer.vala b/vala/valaflowanalyzer.vala index 6f58dd3a0..907c219ea 100644 --- a/vala/valaflowanalyzer.vala +++ b/vala/valaflowanalyzer.vala @@ -599,6 +599,18 @@ public class Vala.FlowAnalyzer : CodeVisitor { } } + public override void visit_with_statement (WithStatement stmt) { + if (unreachable (stmt)) { + return; + } + + current_block.add_node (stmt.expression); + + handle_errors (stmt.expression); + + stmt.body.accept_children (this); + } + public override void visit_if_statement (IfStatement stmt) { if (unreachable (stmt)) { return; diff --git a/vala/valamemberaccess.vala b/vala/valamemberaccess.vala index 1bb805026..1154d3c8b 100644 --- a/vala/valamemberaccess.vala +++ b/vala/valamemberaccess.vala @@ -220,6 +220,8 @@ public class Vala.MemberAccess : Expression { bool may_access_instance_members = false; bool may_access_klass_members = false; + var visited_types = new ArrayList (); + symbol_reference = null; if (qualified) { @@ -278,6 +280,23 @@ public class Vala.MemberAccess : Expression { symbol_reference = SemanticAnalyzer.symbol_lookup_inherited (sym, member_name); + if (symbol_reference == null && sym is WithStatement) { + unowned WithStatement w = (WithStatement) sym; + + var variable_type = w.with_variable.variable_type; + if (variable_type is PointerType) { + variable_type = ((PointerType) variable_type).base_type; + } + visited_types.add (variable_type); + + symbol_reference = variable_type.get_member (member_name); + if (symbol_reference != null) { + inner = new MemberAccess (null, w.with_variable.name, source_reference); + inner.check (context); + may_access_instance_members = true; + } + } + if (symbol_reference == null && sym is TypeSymbol && may_access_instance_members) { // used for generated to_string methods in enums symbol_reference = this_parameter.variable_type.get_member (member_name); @@ -509,7 +528,12 @@ public class Vala.MemberAccess : Expression { } } - Report.error (source_reference, "The name `%s' does not exist in the context of `%s'%s".printf (member_name, base_type_name, base_type_package)); + string visited_types_string = ""; + foreach (var type in visited_types) { + visited_types_string += " or `%s'".printf (type.to_string ()); + } + + Report.error (source_reference, "The name `%s' does not exist in the context of `%s'%s%s".printf (member_name, base_type_name, base_type_package, visited_types_string)); value_type = new InvalidType (); return false; } else if (symbol_reference.error) { diff --git a/vala/valaparser.vala b/vala/valaparser.vala index 2d7fb3e3c..191bc75d5 100644 --- a/vala/valaparser.vala +++ b/vala/valaparser.vala @@ -266,6 +266,7 @@ public class Vala.Parser : CodeVisitor { case TokenType.VOLATILE: case TokenType.WEAK: case TokenType.WHILE: + case TokenType.WITH: case TokenType.YIELD: next (); return; @@ -1630,6 +1631,9 @@ public class Vala.Parser : CodeVisitor { case TokenType.DELETE: stmt = parse_delete_statement (); break; + case TokenType.WITH: + stmt = parse_with_statement (); + break; case TokenType.VAR: is_decl = true; parse_local_variable_declarations (block); @@ -1919,7 +1923,7 @@ public class Vala.Parser : CodeVisitor { expect (TokenType.SEMICOLON); } - LocalVariable parse_local_variable (DataType? variable_type) throws ParseError { + LocalVariable parse_local_variable (DataType? variable_type, bool expect_initializer = false) throws ParseError { var begin = get_location (); string id = parse_identifier (); var type = parse_inline_array_type (variable_type); @@ -1928,6 +1932,10 @@ public class Vala.Parser : CodeVisitor { Expression initializer = null; if (accept (TokenType.ASSIGN)) { initializer = parse_expression (); + } else if (expect_initializer) { + report_parse_error (new ParseError.SYNTAX ("expected initializer")); + prev (); + initializer = new InvalidExpression (); } return new LocalVariable (type, id, initializer, src); } @@ -2275,6 +2283,40 @@ public class Vala.Parser : CodeVisitor { return new DeleteStatement (expr, src); } + Statement? parse_with_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.WITH); + expect (TokenType.OPEN_PARENS); + var expr_or_decl = get_location (); + + LocalVariable? local = null; + + // Try "with (expr)" + Expression expr = parse_expression (); + if (!accept (TokenType.CLOSE_PARENS)) { + // Try "with (var identifier = expr)" + rollback (expr_or_decl); + DataType variable_type; + if (accept (TokenType.UNOWNED) && accept (TokenType.VAR)) { + variable_type = new VarType (false); + } else { + rollback (expr_or_decl); + if (accept (TokenType.VAR)) { + variable_type = new VarType (); + } else { + variable_type = parse_type (true, true); + } + } + local = parse_local_variable (variable_type, true); + expr = local.initializer; + expect (TokenType.CLOSE_PARENS); + } + + var src = get_src (begin); + var body = parse_embedded_statement ("with", false); + return new WithStatement (local, expr, body, src); + } + string parse_attribute_value () throws ParseError { switch (current ()) { case TokenType.NULL: diff --git a/vala/valascanner.vala b/vala/valascanner.vala index a2e76b4d5..7ad6b3c3f 100644 --- a/vala/valascanner.vala +++ b/vala/valascanner.vala @@ -386,7 +386,14 @@ public class Vala.Scanner { if (matches (begin, "void")) return TokenType.VOID; break; case 'w': - if (matches (begin, "weak")) return TokenType.WEAK; + switch (begin[1]) { + case 'e': + if (matches (begin, "weak")) return TokenType.WEAK; + break; + case 'i': + if (matches (begin, "with")) return TokenType.WITH; + break; + } break; } break; diff --git a/vala/valasymbolresolver.vala b/vala/valasymbolresolver.vala index dbe9b1f21..28365127a 100644 --- a/vala/valasymbolresolver.vala +++ b/vala/valasymbolresolver.vala @@ -480,6 +480,10 @@ public class Vala.SymbolResolver : CodeVisitor { list.accept_children (this); } + public override void visit_with_statement (WithStatement stmt) { + stmt.accept_children (this); + } + public override void visit_expression_statement (ExpressionStatement stmt) { if (stmt.checked) { return; diff --git a/vala/valatokentype.vala b/vala/valatokentype.vala index 75cf92e6c..9cc6d1c74 100644 --- a/vala/valatokentype.vala +++ b/vala/valatokentype.vala @@ -153,6 +153,7 @@ public enum Vala.TokenType { VOLATILE, WEAK, WHILE, + WITH, YIELD; public unowned string to_string () { @@ -286,6 +287,7 @@ public enum Vala.TokenType { case VOLATILE: return "`volatile'"; case WEAK: return "`weak'"; case WHILE: return "`while'"; + case WITH: return "`with'"; case YIELD: return "`yield'"; default: return "unknown token"; } diff --git a/vala/valawithstatement.vala b/vala/valawithstatement.vala new file mode 100644 index 000000000..f873e600c --- /dev/null +++ b/vala/valawithstatement.vala @@ -0,0 +1,150 @@ +/* valawithtatement.vala + * + * Copyright (C) 2020 Nick Schrader + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Nick Schrader + */ + +public class Vala.WithStatement : Block { + /** + * Expression representing the type of body's dominant scope. + */ + public Expression expression { + get { return _expression; } + private set { + _expression = value; + _expression.parent_node = this; + } + } + + /** + * Specifies the with-variable. + */ + public LocalVariable? with_variable { get; private set; } + + /** + * The block which dominant scope is type of expression. + */ + public Block body { + get { return _body; } + private set { + _body = value; + _body.parent_node = this; + } + } + + Expression _expression; + Block _body; + + public WithStatement (LocalVariable? variable, Expression expression, Block body, SourceReference? source_reference = null) { + base (source_reference); + this.with_variable = variable; + this.expression = expression; + this.body = body; + } + + public override void accept (CodeVisitor visitor) { + visitor.visit_with_statement (this); + } + + public override void accept_children (CodeVisitor visitor) { + if (expression.symbol_reference == with_variable) { + expression.accept (visitor); + } + + if (with_variable != null) { + with_variable.accept (visitor); + } + + body.accept (visitor); + } + + bool is_object_or_value_type (DataType? type) { + if (type == null) { + return false; + } else if (type is PointerType) { + var pointer_type = (PointerType) type; + return is_object_or_value_type (pointer_type.base_type) && expression is PointerIndirection; + } else { + return type is ObjectType || type is ValueType; + } + } + + public override bool check (CodeContext context) { + if (checked) { + return !error; + } + + checked = true; + + if (!expression.check (context)) { + error = true; + return false; + } + + if (!is_object_or_value_type (expression.value_type)) { + error = true; + Report.error (expression.source_reference, "with statement expects an object or basic type"); + return false; + } + + var local_var = expression.symbol_reference as LocalVariable; + if (with_variable != null || local_var == null) { + if (with_variable == null) { + local_var = new LocalVariable (expression.value_type.copy (), get_temp_name (), expression, source_reference); + } else { + local_var = with_variable; + } + body.insert_statement (0, new DeclarationStatement (local_var, source_reference)); + } + with_variable = local_var; + + var old_symbol = context.analyzer.current_symbol; + owner = context.analyzer.current_symbol.scope; + context.analyzer.current_symbol = this; + + if (!body.check (context)) { + error = true; + } + + context.analyzer.current_symbol = old_symbol; + + return !error; + } + + public override void emit (CodeGenerator codegen) { + if (expression.symbol_reference == with_variable) { + expression.emit (codegen); + } + body.emit (codegen); + } + + public override void get_error_types (Collection collection, SourceReference? source_reference = null) { + if (source_reference == null) { + source_reference = this.source_reference; + } + expression.get_error_types (collection, source_reference); + body.get_error_types (collection, source_reference); + } + + public override void get_defined_variables (Collection collection) { + if (expression.symbol_reference != with_variable) { + collection.add (with_variable); + } + } +}