From 31d30658306f15160775e9a125b0dad4ab155332 Mon Sep 17 00:00:00 2001 From: Nick Schrader Date: Fri, 27 Mar 2020 13:12:09 -0300 Subject: Add support for "with" statement Create data-type scoped blocks Fixes https://gitlab.gnome.org/GNOME/vala/issues/327 --- tests/Makefile.am | 30 +++++ tests/nullability/with-non-null.test | 12 ++ tests/objects/with-expression.vala | 38 ++++++ tests/objects/with-instance.vala | 73 ++++++++++++ tests/objects/with-nested.vala | 34 ++++++ tests/parser/with-embedded.vala | 4 + tests/parser/with-empty.vala | 3 + tests/parser/with-invalid-declaration.test | 6 + tests/parser/with-invalid-expression.test | 6 + tests/parser/with-no-block.test | 5 + tests/parser/with-no-expression.test | 6 + tests/semantic/with-array.test | 7 ++ tests/semantic/with-buildin.vala | 5 + tests/semantic/with-class.test | 9 ++ tests/semantic/with-compact.vala | 13 ++ tests/semantic/with-declaration-cast-type.vala | 10 ++ tests/semantic/with-declaration-wrong-type.test | 12 ++ tests/semantic/with-declaration.vala | 35 ++++++ tests/semantic/with-dereferenced-pointer.vala | 14 +++ tests/semantic/with-enum-member.vala | 8 ++ tests/semantic/with-enum.test | 10 ++ tests/semantic/with-error-member.test | 11 ++ tests/semantic/with-error.test | 10 ++ tests/semantic/with-namespace.test | 9 ++ tests/semantic/with-no-declaration.test | 9 ++ tests/semantic/with-no-such-member.test | 10 ++ tests/semantic/with-null.vala | 12 ++ tests/semantic/with-outside-declaration.test | 13 ++ tests/semantic/with-pointer.test | 7 ++ tests/semantic/with-string.vala | 5 + tests/semantic/with-value.vala | 10 ++ vala/Makefile.am | 1 + vala/valacodevisitor.vala | 8 ++ vala/valacodewriter.vala | 10 ++ vala/valaflowanalyzer.vala | 12 ++ vala/valamemberaccess.vala | 26 +++- vala/valaparser.vala | 43 ++++++- vala/valascanner.vala | 9 +- vala/valasymbolresolver.vala | 4 + vala/valatokentype.vala | 2 + vala/valawithstatement.vala | 150 ++++++++++++++++++++++++ 41 files changed, 698 insertions(+), 3 deletions(-) create mode 100644 tests/nullability/with-non-null.test create mode 100644 tests/objects/with-expression.vala create mode 100644 tests/objects/with-instance.vala create mode 100644 tests/objects/with-nested.vala create mode 100644 tests/parser/with-embedded.vala create mode 100644 tests/parser/with-empty.vala create mode 100644 tests/parser/with-invalid-declaration.test create mode 100644 tests/parser/with-invalid-expression.test create mode 100644 tests/parser/with-no-block.test create mode 100644 tests/parser/with-no-expression.test create mode 100644 tests/semantic/with-array.test create mode 100644 tests/semantic/with-buildin.vala create mode 100644 tests/semantic/with-class.test create mode 100644 tests/semantic/with-compact.vala create mode 100644 tests/semantic/with-declaration-cast-type.vala create mode 100644 tests/semantic/with-declaration-wrong-type.test create mode 100644 tests/semantic/with-declaration.vala create mode 100644 tests/semantic/with-dereferenced-pointer.vala create mode 100644 tests/semantic/with-enum-member.vala create mode 100644 tests/semantic/with-enum.test create mode 100644 tests/semantic/with-error-member.test create mode 100644 tests/semantic/with-error.test create mode 100644 tests/semantic/with-namespace.test create mode 100644 tests/semantic/with-no-declaration.test create mode 100644 tests/semantic/with-no-such-member.test create mode 100644 tests/semantic/with-null.vala create mode 100644 tests/semantic/with-outside-declaration.test create mode 100644 tests/semantic/with-pointer.test create mode 100644 tests/semantic/with-string.vala create mode 100644 tests/semantic/with-value.vala create mode 100644 vala/valawithstatement.vala diff --git a/tests/Makefile.am b/tests/Makefile.am index 78c939a2f..7ff4ff350 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -504,6 +504,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 \ @@ -733,6 +736,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 \ @@ -927,6 +936,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 \ @@ -940,6 +969,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 9f0b976fc..e4bc73ac8 100644 --- a/vala/valaparser.vala +++ b/vala/valaparser.vala @@ -1617,6 +1617,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); @@ -1906,7 +1909,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); @@ -1915,6 +1918,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); } @@ -2262,6 +2269,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); + } + } +} -- cgit v1.2.1