Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(208)

Side by Side Diff: pkg/kernel/lib/ast.dart

Issue 3008853002: Proposed kernel AST changes for annotating parameter type checks. (Closed)
Patch Set: Created 3 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /// ----------------------------------------------------------------------- 5 /// -----------------------------------------------------------------------
6 /// ERROR HANDLING 6 /// ERROR HANDLING
7 /// ----------------------------------------------------------------------- 7 /// -----------------------------------------------------------------------
8 /// 8 ///
9 /// As a rule of thumb, errors that can be detected statically are handled by 9 /// As a rule of thumb, errors that can be detected statically are handled by
10 /// the frontend, typically by translating the erroneous code into a 'throw' or 10 /// the frontend, typically by translating the erroneous code into a 'throw' or
(...skipping 959 matching lines...) Expand 10 before | Expand all | Expand 10 after
970 /// The implied getter and setter for the field are not represented explicitly, 970 /// The implied getter and setter for the field are not represented explicitly,
971 /// but can be made explicit if needed. 971 /// but can be made explicit if needed.
972 class Field extends Member { 972 class Field extends Member {
973 DartType type; // Not null. Defaults to DynamicType. 973 DartType type; // Not null. Defaults to DynamicType.
974 int flags = 0; 974 int flags = 0;
975 Expression initializer; // May be null. 975 Expression initializer; // May be null.
976 976
977 /// The uri of the source file this field was loaded from. 977 /// The uri of the source file this field was loaded from.
978 String fileUri; 978 String fileUri;
979 979
980 /// Formal safety of the implicit setter's formal parameter (if there is one).
981 ///
982 /// See [FormalSafety] for details.
983 FormalSafety setterFormalSafety = FormalSafety.unsafe;
984
985 /// Interface safety of the implicit setter's formal parameter (if there is
986 /// one).
987 ///
988 /// See [InterfaceSafety] for details.
989 InterfaceSafety setterInterfaceSafety = InterfaceSafety.semiTyped;
990
980 Field(Name name, 991 Field(Name name,
981 {this.type: const DynamicType(), 992 {this.type: const DynamicType(),
982 this.initializer, 993 this.initializer,
983 bool isCovariant: false, 994 bool isCovariant: false,
984 bool isFinal: false, 995 bool isFinal: false,
985 bool isConst: false, 996 bool isConst: false,
986 bool isStatic: false, 997 bool isStatic: false,
987 bool hasImplicitGetter, 998 bool hasImplicitGetter,
988 bool hasImplicitSetter, 999 bool hasImplicitSetter,
989 int transformerFlags: 0, 1000 int transformerFlags: 0,
(...skipping 750 matching lines...) Expand 10 before | Expand all | Expand 10 after
1740 } 1751 }
1741 } 1752 }
1742 } 1753 }
1743 1754
1744 /// Expression of form `x.field`. 1755 /// Expression of form `x.field`.
1745 /// 1756 ///
1746 /// This may invoke a getter, read a field, or tear off a method. 1757 /// This may invoke a getter, read a field, or tear off a method.
1747 class PropertyGet extends Expression { 1758 class PropertyGet extends Expression {
1748 Expression receiver; 1759 Expression receiver;
1749 Name name; 1760 Name name;
1761 DispatchCategory dispatchCategory = DispatchCategory.dynamicDispatch;
1750 1762
1751 Reference interfaceTargetReference; 1763 Reference interfaceTargetReference;
1752 1764
1753 PropertyGet(Expression receiver, Name name, [Member interfaceTarget]) 1765 PropertyGet(Expression receiver, Name name, [Member interfaceTarget])
1754 : this.byReference(receiver, name, getMemberReference(interfaceTarget)); 1766 : this.byReference(receiver, name, getMemberReference(interfaceTarget));
1755 1767
1756 PropertyGet.byReference( 1768 PropertyGet.byReference(
1757 this.receiver, this.name, this.interfaceTargetReference) { 1769 this.receiver, this.name, this.interfaceTargetReference) {
1758 receiver?.parent = this; 1770 receiver?.parent = this;
1759 } 1771 }
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
1848 value = value.accept(v); 1860 value = value.accept(v);
1849 value?.parent = this; 1861 value?.parent = this;
1850 } 1862 }
1851 } 1863 }
1852 } 1864 }
1853 1865
1854 /// Directly read a field, call a getter, or tear off a method. 1866 /// Directly read a field, call a getter, or tear off a method.
1855 class DirectPropertyGet extends Expression { 1867 class DirectPropertyGet extends Expression {
1856 Expression receiver; 1868 Expression receiver;
1857 Reference targetReference; 1869 Reference targetReference;
1870 DispatchCategory dispatchCategory = DispatchCategory.dynamicDispatch;
1858 1871
1859 DirectPropertyGet(Expression receiver, Member target) 1872 DirectPropertyGet(Expression receiver, Member target)
1860 : this.byReference(receiver, getMemberReference(target)); 1873 : this.byReference(receiver, getMemberReference(target));
1861 1874
1862 DirectPropertyGet.byReference(this.receiver, this.targetReference) { 1875 DirectPropertyGet.byReference(this.receiver, this.targetReference) {
1863 receiver?.parent = this; 1876 receiver?.parent = this;
1864 } 1877 }
1865 1878
1866 Member get target => targetReference?.asMember; 1879 Member get target => targetReference?.asMember;
1867 1880
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
1937 accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertySet(this, arg); 1950 accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertySet(this, arg);
1938 1951
1939 DartType getStaticType(TypeEnvironment types) => value.getStaticType(types); 1952 DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
1940 } 1953 }
1941 1954
1942 /// Directly call an instance method, bypassing ordinary dispatch. 1955 /// Directly call an instance method, bypassing ordinary dispatch.
1943 class DirectMethodInvocation extends InvocationExpression { 1956 class DirectMethodInvocation extends InvocationExpression {
1944 Expression receiver; 1957 Expression receiver;
1945 Reference targetReference; 1958 Reference targetReference;
1946 Arguments arguments; 1959 Arguments arguments;
1960 DispatchCategory dispatchCategory = DispatchCategory.dynamicDispatch;
1947 1961
1948 DirectMethodInvocation( 1962 DirectMethodInvocation(
1949 Expression receiver, Procedure target, Arguments arguments) 1963 Expression receiver, Procedure target, Arguments arguments)
1950 : this.byReference(receiver, getMemberReference(target), arguments); 1964 : this.byReference(receiver, getMemberReference(target), arguments);
1951 1965
1952 DirectMethodInvocation.byReference( 1966 DirectMethodInvocation.byReference(
1953 this.receiver, this.targetReference, this.arguments) { 1967 this.receiver, this.targetReference, this.arguments) {
1954 receiver?.parent = this; 1968 receiver?.parent = this;
1955 arguments?.parent = this; 1969 arguments?.parent = this;
1956 } 1970 }
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
1999 .substituteType(returnType); 2013 .substituteType(returnType);
2000 } 2014 }
2001 } 2015 }
2002 2016
2003 /// Expression of form `super.field`. 2017 /// Expression of form `super.field`.
2004 /// 2018 ///
2005 /// This may invoke a getter, read a field, or tear off a method. 2019 /// This may invoke a getter, read a field, or tear off a method.
2006 class SuperPropertyGet extends Expression { 2020 class SuperPropertyGet extends Expression {
2007 Name name; 2021 Name name;
2008 Reference interfaceTargetReference; 2022 Reference interfaceTargetReference;
2023 DispatchCategory get dispatchCategory => DispatchCategory.viaThis;
2009 2024
2010 SuperPropertyGet(Name name, [Member interfaceTarget]) 2025 SuperPropertyGet(Name name, [Member interfaceTarget])
2011 : this.byReference(name, getMemberReference(interfaceTarget)); 2026 : this.byReference(name, getMemberReference(interfaceTarget));
2012 2027
2013 SuperPropertyGet.byReference(this.name, this.interfaceTargetReference); 2028 SuperPropertyGet.byReference(this.name, this.interfaceTargetReference);
2014 2029
2015 Member get interfaceTarget => interfaceTargetReference?.asMember; 2030 Member get interfaceTarget => interfaceTargetReference?.asMember;
2016 2031
2017 void set interfaceTarget(Member member) { 2032 void set interfaceTarget(Member member) {
2018 interfaceTargetReference = getMemberReference(member); 2033 interfaceTargetReference = getMemberReference(member);
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after
2216 /// 2231 ///
2217 /// May be `null` if the target is a synthetic static member without a name. 2232 /// May be `null` if the target is a synthetic static member without a name.
2218 Name get name; 2233 Name get name;
2219 } 2234 }
2220 2235
2221 /// Expression of form `x.foo(y)`. 2236 /// Expression of form `x.foo(y)`.
2222 class MethodInvocation extends InvocationExpression { 2237 class MethodInvocation extends InvocationExpression {
2223 Expression receiver; 2238 Expression receiver;
2224 Name name; 2239 Name name;
2225 Arguments arguments; 2240 Arguments arguments;
2241 DispatchCategory dispatchCategory = DispatchCategory.dynamicDispatch;
2226 2242
2227 Reference interfaceTargetReference; 2243 Reference interfaceTargetReference;
2228 2244
2229 MethodInvocation(Expression receiver, Name name, Arguments arguments, 2245 MethodInvocation(Expression receiver, Name name, Arguments arguments,
2230 [Member interfaceTarget]) 2246 [Member interfaceTarget])
2231 : this.byReference( 2247 : this.byReference(
2232 receiver, name, arguments, getMemberReference(interfaceTarget)); 2248 receiver, name, arguments, getMemberReference(interfaceTarget));
2233 2249
2234 MethodInvocation.byReference( 2250 MethodInvocation.byReference(
2235 this.receiver, this.name, this.arguments, this.interfaceTargetReference) { 2251 this.receiver, this.name, this.arguments, this.interfaceTargetReference) {
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
2303 } 2319 }
2304 } 2320 }
2305 } 2321 }
2306 2322
2307 /// Expression of form `super.foo(x)`. 2323 /// Expression of form `super.foo(x)`.
2308 /// 2324 ///
2309 /// The provided arguments might not match the parameters of the target. 2325 /// The provided arguments might not match the parameters of the target.
2310 class SuperMethodInvocation extends InvocationExpression { 2326 class SuperMethodInvocation extends InvocationExpression {
2311 Name name; 2327 Name name;
2312 Arguments arguments; 2328 Arguments arguments;
2329 DispatchCategory get dispatchCategory => DispatchCategory.viaThis;
2313 2330
2314 Reference interfaceTargetReference; 2331 Reference interfaceTargetReference;
2315 2332
2316 SuperMethodInvocation(Name name, Arguments arguments, 2333 SuperMethodInvocation(Name name, Arguments arguments,
2317 [Procedure interfaceTarget]) 2334 [Procedure interfaceTarget])
2318 : this.byReference(name, arguments, getMemberReference(interfaceTarget)); 2335 : this.byReference(name, arguments, getMemberReference(interfaceTarget));
2319 2336
2320 SuperMethodInvocation.byReference( 2337 SuperMethodInvocation.byReference(
2321 this.name, this.arguments, this.interfaceTargetReference) { 2338 this.name, this.arguments, this.interfaceTargetReference) {
2322 arguments?.parent = this; 2339 arguments?.parent = this;
(...skipping 1439 matching lines...) Expand 10 before | Expand all | Expand 10 after
3762 } 3779 }
3763 3780
3764 transformChildren(Transformer v) { 3781 transformChildren(Transformer v) {
3765 if (expression != null) { 3782 if (expression != null) {
3766 expression = expression.accept(v); 3783 expression = expression.accept(v);
3767 expression?.parent = this; 3784 expression?.parent = this;
3768 } 3785 }
3769 } 3786 }
3770 } 3787 }
3771 3788
3789 /// Indication of when a runtime type check of a formal parameter (or type
3790 /// parameter) needs to be included in the code generated for a method.
3791 ///
3792 /// [FormalSafety] annotations are considered to be part of a method's body;
3793 /// they only apply to concrete methods, and they affect any calls that resolve
3794 /// to the annotated method at runtime. So for instance, in the following code,
3795 /// the "unsafe" annotation means that the type of `o` will have to be checked
3796 /// in the second call to `g` (when the runtime type of `c` is `D`), but not in
3797 /// the first.
3798 ///
3799 /// class C {
3800 /// void f(Object o /*safe*/) { ... }
3801 /// }
3802 /// class D {
3803 /// void f(covariant int o /*unsafe*/) { ... }
3804 /// }
3805 /// void g(C c) {
3806 /// c.f('hi');
3807 /// }
3808 /// void main() {
3809 /// g(new C());
3810 /// g(new D());
3811 /// }
3812 enum FormalSafety {
3813 /// Full safety; a runtime check is only needed for dynamic invocations.
3814 ///
3815 /// For a [FormalParameterDeclaration], the type system can guarantee that the
3816 /// actual value that will be passed to the method at runtime will be an
3817 /// instance of the formal parameter's type
3818 /// ([FormalParameterDeclaration.type]), *provided that* the call site is not
3819 /// annotated as [DispatchCategory.dynamicDispatch].
3820 ///
3821 /// For a [TypeParameter], the type system can guarantee that the actual type
3822 /// that will be used to instantiate the type parameter at runtime will be a
3823 /// subtype of the type parameter's bound ([TypeParameter.bound]),
3824 /// *provided that* the call site is not annotated as
3825 /// [DispatchCategory.dynamicDispatch].
3826 ///
3827 /// This annotation is used for static and top level methods since they never
3828 /// require additional runtime checks due to covariance.
3829 safe,
3830
3831 /// Partial safety; a runtime check is not needed for "typed" or "this"
3832 /// invocations.
3833 ///
3834 /// For a [FormalParameterDeclaration], the type system can guarantee that the
3835 /// actual value that will be passed to the method at runtime will be an
3836 /// instance of the formal parameter's type
3837 /// ([FormalParameterDeclaration.type]), *provided that* the invocation comes
3838 /// through an invocation target that marks the corresponding type parameter
3839 /// with [InterfaceSafety.typed], or the call site is annotated as
3840 /// [DispatchCategory.viaThis].
3841 ///
3842 /// For a [TypeParameter], the type system can guarantee that the actual type
3843 /// that will be used to instantiate the type parameter at runtime will be a
3844 /// subtype of the type parameter's bound ([TypeParameter.bound]),
3845 /// *provided that* the invocation comes through an invocation target that
3846 /// marks the corresponding type parameter with [InterfaceSafety.typed], or
3847 /// the call site is annotated as [DispatchCategory.viaThis].
3848 semiSafe,
3849
3850 /// No safety; a runtime type check is always required.
3851 ///
3852 /// For a [FormalParameterDeclaration], the type system cannot guarantee that
3853 /// the actual value that will be passed to the method at runtime will be an
3854 /// instance of the formal parameter's type
3855 /// ([FormalParameterDeclaration.type]). Therefore, in the absence of
3856 /// additional information from whole program analysis, a runtime type check
3857 /// needs to be compiled into the body of the method.
3858 ///
3859 /// Not used for [TypeParameter]s.
3860 unsafe,
3861 }
3862
3863 /// Indication of when a call site can skip a runtime type check that would have
3864 /// otherwise been required by [FormalSafety].
3865 ///
3866 /// [InterfaceSafety] annotations are considered to be part of a class's API;
3867 /// they apply to both concrete and abstract methods, and they affect any calls
3868 /// that resolve to the annotated method statically. So for instance, in the
3869 /// following code, the "semi-typed" annotation means that the call site at g1
3870 /// (which statically resolves to C.f) needs a runtime type check, but the call
3871 /// site at g2 (which statically resolves to D.f) does not.
3872 ///
3873 /// class C<T> {
3874 /// void f(T x /*semi-typed*/) { ... }
3875 /// }
3876 /// class D extends C<num> {
Jennifer Messerly 2017/08/29 23:02:23 one thing that might be worth an example? class C
Paul Berry 2017/08/29 23:24:24 Good idea. Done.
3877 /// void f(num x /*typed*/);
3878 /// }
3879 /// void g1(C<num> c) {
3880 /// c.f(1.5);
3881 /// }
3882 /// void g2(D d) {
3883 /// d.f(1.5);
3884 /// }
3885 enum InterfaceSafety {
3886 /// Full type guarantee; a runtime check is only needed if the concrete
3887 /// parameter bound at runtime is "unsafe".
3888 ///
3889 /// This annotation is used for static and top level methods since they never
3890 /// require additional runtime checks due to covariance.
3891 ///
3892 /// See [FormalSafety] for details.
3893 typed,
3894
3895 /// Partial type guarantee; a runtime check is needed if the concrete
3896 /// parameter bound at runtime is "unsafe" or "semiSafe".
3897 ///
3898 /// See [FormalSafety] for details.
3899 semiTyped,
3900 }
3901
3902 /// Categorization of a call site indicating its effect on type guarantees.
3903 enum DispatchCategory {
3904 /// This call site binds to its callee through a specific interface.
3905 ///
3906 /// The front end guarantees that the target of the call exists, has the
3907 /// correct arity, and accepts all of the supplied named parameters. Further,
3908 /// it guarantees that the number of type parameters supplied matches the
3909 /// number of type parameters expected by the target of the call.
3910 interface,
3911
3912 /// This call site binds to its callee via a call on `this`.
3913 ///
3914 /// Similar to [interface], however the target of the call is a method on
3915 /// `this` or `super`, therefore all of the class's type parameters are known
3916 /// to match exactly.
3917 viaThis,
3918
3919 /// This call site is an invocation of a function object (formed either by a
3920 /// tear off or a function literal).
3921 ///
3922 /// Similar to [interface], however the interface target of the call is not
3923 /// known.
3924 closure,
3925
3926 /// The call site is dynamic.
3927 ///
3928 /// The front end makes no guarantees that the target of the call will accept
3929 /// the actual runtime types of the parameters, nor that the target of the
3930 /// call even exists. Everything must be checked at runtime.
3931 dynamicDispatch,
3932 }
3933
3772 /// Declaration of a local variable. 3934 /// Declaration of a local variable.
3773 /// 3935 ///
3774 /// This may occur as a statement, but is also used in several non-statement 3936 /// This may occur as a statement, but is also used in several non-statement
3775 /// contexts, such as in [ForStatement], [Catch], and [FunctionNode]. 3937 /// contexts, such as in [ForStatement], [Catch], and [FunctionNode].
3776 /// 3938 ///
3777 /// When this occurs as a statement, it must be a direct child of a [Block]. 3939 /// When this occurs as a statement, it must be a direct child of a [Block].
3778 // 3940 //
3779 // DESIGN TODO: Should we remove the 'final' modifier from variables? 3941 // DESIGN TODO: Should we remove the 'final' modifier from variables?
3780 class VariableDeclaration extends Statement { 3942 class VariableDeclaration extends Statement {
3781 /// Offset of the equals sign in the source file it comes from. 3943 /// Offset of the equals sign in the source file it comes from.
(...skipping 14 matching lines...) Expand all
3796 3958
3797 /// Offset of the declaration, set and used when writing the binary. 3959 /// Offset of the declaration, set and used when writing the binary.
3798 int binaryOffsetNoTag = -1; 3960 int binaryOffsetNoTag = -1;
3799 3961
3800 /// For locals, this is the initial value. 3962 /// For locals, this is the initial value.
3801 /// For parameters, this is the default value. 3963 /// For parameters, this is the default value.
3802 /// 3964 ///
3803 /// Should be null in other cases. 3965 /// Should be null in other cases.
3804 Expression initializer; // May be null. 3966 Expression initializer; // May be null.
3805 3967
3968 /// If this is a formal parameter of a concrete method, its formal safety.
3969 /// Otherwise ignored.
3970 ///
3971 /// See [FormalSafety] for details.
3972 FormalSafety formalSafety = FormalSafety.safe;
3973
3974 /// If this is a formal parameter of a method, its interface safety.
3975 /// Otherwise ignored.
3976 ///
3977 /// See [InterfaceSafety] for details.
3978 InterfaceSafety interfaceSafety = InterfaceSafety.typed;
3979
3806 VariableDeclaration(this.name, 3980 VariableDeclaration(this.name,
3807 {this.initializer, 3981 {this.initializer,
3808 this.type: const DynamicType(), 3982 this.type: const DynamicType(),
3809 bool isFinal: false, 3983 bool isFinal: false,
3810 bool isConst: false, 3984 bool isConst: false,
3811 bool isFieldFormal: false, 3985 bool isFieldFormal: false,
3812 bool isCovariant: false}) { 3986 bool isCovariant: false}) {
3813 assert(type != null); 3987 assert(type != null);
3814 initializer?.parent = this; 3988 initializer?.parent = this;
3815 this.isFinal = isFinal; 3989 this.isFinal = isFinal;
(...skipping 599 matching lines...) Expand 10 before | Expand all | Expand 10 after
4415 /// different [FunctionType] objects. 4589 /// different [FunctionType] objects.
4416 class TypeParameter extends TreeNode { 4590 class TypeParameter extends TreeNode {
4417 String name; // Cosmetic name. 4591 String name; // Cosmetic name.
4418 4592
4419 /// The bound on the type variable. 4593 /// The bound on the type variable.
4420 /// 4594 ///
4421 /// Should not be null except temporarily during IR construction. Should 4595 /// Should not be null except temporarily during IR construction. Should
4422 /// be set to the root class for type parameters without an explicit bound. 4596 /// be set to the root class for type parameters without an explicit bound.
4423 DartType bound; 4597 DartType bound;
4424 4598
4599 /// If this is a type parameter of a concrete generic method, its formal
4600 /// safety. Otherwise ignored.
4601 ///
4602 /// See [FormalSafety] for details.
4603 FormalSafety formalSafety = FormalSafety.safe;
4604
4605 /// If this is a type parameter of a generic method, its interface safety.
4606 /// Otherwise ignored.
4607 ///
4608 /// See [InterfaceSafety] for details.
4609 InterfaceSafety interfaceSafety = InterfaceSafety.typed;
4610
4425 TypeParameter([this.name, this.bound]); 4611 TypeParameter([this.name, this.bound]);
4426 4612
4427 accept(TreeVisitor v) => v.visitTypeParameter(this); 4613 accept(TreeVisitor v) => v.visitTypeParameter(this);
4428 4614
4429 visitChildren(Visitor v) { 4615 visitChildren(Visitor v) {
4430 bound.accept(v); 4616 bound.accept(v);
4431 } 4617 }
4432 4618
4433 transformChildren(Transformer v) { 4619 transformChildren(Transformer v) {
4434 bound = v.visitDartType(bound); 4620 bound = v.visitDartType(bound);
(...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after
4763 if (typedef_.canonicalName == null) { 4949 if (typedef_.canonicalName == null) {
4764 throw '$typedef_ has no canonical name'; 4950 throw '$typedef_ has no canonical name';
4765 } 4951 }
4766 return typedef_.canonicalName; 4952 return typedef_.canonicalName;
4767 } 4953 }
4768 4954
4769 /// Annotation describing information which is not part of Dart semantics; in 4955 /// Annotation describing information which is not part of Dart semantics; in
4770 /// other words, if this information (or any information it refers to) changes, 4956 /// other words, if this information (or any information it refers to) changes,
4771 /// static analysis and runtime behavior of the library are unaffected. 4957 /// static analysis and runtime behavior of the library are unaffected.
4772 const informative = null; 4958 const informative = null;
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698