Skip to content

Commit acc95d3

Browse files
author
John Messerly
committed
fixes #24507, allow known functions to be treated as strict arrows
In a known function, * -> * will be top -> top. In all other function types, it will still be bottom -> top. [email protected] Review URL: https://codereview.chromium.org/1700523002 .
1 parent 47119ed commit acc95d3

File tree

4 files changed

+165
-31
lines changed

4 files changed

+165
-31
lines changed

pkg/analyzer/lib/src/generated/type_system.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -284,18 +284,15 @@ class StrongTypeSystemImpl implements TypeSystem {
284284
/**
285285
* Check that [f1] is a subtype of [f2].
286286
*
287-
* [fuzzyArrows] indicates whether or not the f1 and f2 should be
288-
* treated as fuzzy arrow types (and hence dynamic parameters to f2 treated
289-
* as bottom).
287+
* This will always assume function types use fuzzy arrows, in other words
288+
* that dynamic parameters of f1 and f2 are treated as bottom.
290289
*/
291-
bool _isFunctionSubtypeOf(FunctionType f1, FunctionType f2,
292-
{bool fuzzyArrows: true}) {
293-
290+
bool _isFunctionSubtypeOf(FunctionType f1, FunctionType f2) {
294291
return FunctionTypeImpl.relate(
295292
f1,
296293
f2,
297294
(DartType t1, DartType t2) =>
298-
_isSubtypeOf(t2, t1, null, dynamicIsBottom: fuzzyArrows),
295+
_isSubtypeOf(t2, t1, null, dynamicIsBottom: true),
299296
instantiateToBounds,
300297
returnRelation: isSubtypeOf);
301298
}

pkg/analyzer/lib/src/task/strong/checker.dart

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:analyzer/dart/ast/token.dart' show Token, TokenType;
1212
import 'package:analyzer/dart/ast/visitor.dart';
1313
import 'package:analyzer/dart/element/element.dart';
1414
import 'package:analyzer/dart/element/type.dart';
15+
import 'package:analyzer/src/dart/element/element.dart';
1516
import 'package:analyzer/src/dart/element/type.dart';
1617
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
1718
import 'package:analyzer/src/generated/type_system.dart';
@@ -565,7 +566,7 @@ class CodeChecker extends RecursiveAstVisitor {
565566
}
566567

567568
StaticInfo _checkAssignment(Expression expr, DartType toT) {
568-
final fromT = expr.staticType ?? DynamicTypeImpl.instance;
569+
final fromT = _getStaticType(expr);
569570
final Coercion c = _coerceTo(fromT, toT);
570571
if (c is Identity) return null;
571572
if (c is CoercionError) return new StaticTypeError(rules, expr, toT);
@@ -770,7 +771,63 @@ class CodeChecker extends RecursiveAstVisitor {
770771
}
771772

772773
DartType _getStaticType(Expression expr) {
773-
return expr.staticType ?? DynamicTypeImpl.instance;
774+
DartType t = expr.staticType ?? DynamicTypeImpl.instance;
775+
776+
// Remove fuzzy arrow if possible.
777+
if (t is FunctionType && StaticInfo.isKnownFunction(expr)) {
778+
t = _removeFuzz(t);
779+
}
780+
781+
return t;
782+
}
783+
784+
/// Remove "fuzzy arrow" in this function type.
785+
///
786+
/// Normally we treat dynamically typed parameters as bottom for function
787+
/// types. This allows type tests such as `if (f is SingleArgFunction)`.
788+
/// It also requires a dynamic check on the parameter type to call these
789+
/// functions.
790+
///
791+
/// When we convert to a strict arrow, dynamically typed parameters become
792+
/// top. This is safe to do for known functions, like top-level or local
793+
/// functions and static methods. Those functions must already be essentially
794+
/// treating dynamic as top.
795+
///
796+
/// Only the outer-most arrow can be strict. Any others must be fuzzy, because
797+
/// we don't know what function value will be passed there.
798+
// TODO(jmesserly): should we use a real "fuzzyArrow" bit on the function
799+
// type? That would allow us to implement this in the subtype relation.
800+
// TODO(jmesserly): we'll need to factor this differently if we want to
801+
// move CodeChecker's functionality into existing analyzer. Likely we can
802+
// let the Expression have a strict arrow, then in places were we do
803+
// inference, convert back to a fuzzy arrow.
804+
FunctionType _removeFuzz(FunctionType t) {
805+
bool foundFuzz = false;
806+
List<ParameterElement> parameters = <ParameterElement>[];
807+
for (ParameterElement p in t.parameters) {
808+
ParameterElement newP = _removeParameterFuzz(p);
809+
parameters.add(newP);
810+
if (p != newP) foundFuzz = true;
811+
}
812+
if (!foundFuzz) {
813+
return t;
814+
}
815+
816+
FunctionElementImpl function = new FunctionElementImpl("", -1);
817+
function.synthetic = true;
818+
function.returnType = t.returnType;
819+
function.shareTypeParameters(t.typeFormals);
820+
function.shareParameters(parameters);
821+
return function.type = new FunctionTypeImpl(function);
822+
}
823+
824+
/// Removes fuzzy arrow, see [_removeFuzz].
825+
ParameterElement _removeParameterFuzz(ParameterElement p) {
826+
if (p.type.isDynamic) {
827+
return new ParameterElementImpl.synthetic(
828+
p.name, typeProvider.objectType, p.parameterKind);
829+
}
830+
return p;
774831
}
775832

776833
/// Given an expression, return its type assuming it is

pkg/analyzer/lib/src/task/strong/info.dart

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,7 @@ abstract class DownCast extends CoercionInfo {
138138
}
139139
}
140140

141-
Element element = null;
142-
if (expression is PropertyAccess) {
143-
element = expression.propertyName.staticElement;
144-
} else if (expression is Identifier) {
145-
element = expression.staticElement;
146-
}
147-
// First class functions and static methods, where we know the original
148-
// declaration, will have an exact type, so we know a downcast will fail.
149-
if (element is FunctionElement ||
150-
element is MethodElement && element.isStatic) {
141+
if (StaticInfo.isKnownFunction(expression)) {
151142
return new StaticTypeError(rules, expression, toT);
152143
}
153144

@@ -551,6 +542,19 @@ abstract class StaticInfo {
551542
// TODO(jmesserly): review the usage of error codes. We probably want our own,
552543
// as well as some DDC specific [ErrorType]s.
553544
ErrorCode toErrorCode();
545+
546+
static bool isKnownFunction(Expression expression) {
547+
Element element = null;
548+
if (expression is PropertyAccess) {
549+
element = expression.propertyName.staticElement;
550+
} else if (expression is Identifier) {
551+
element = expression.staticElement;
552+
}
553+
// First class functions and static methods, where we know the original
554+
// declaration, will have an exact type, so we know a downcast will fail.
555+
return element is FunctionElement ||
556+
element is MethodElement && element.isStatic;
557+
}
554558
}
555559

556560
class StaticTypeError extends StaticError {

pkg/analyzer/test/src/task/strong/checker_test.dart

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -556,12 +556,11 @@ void main() {
556556
typedef A Right(dynamic x); // Right branch
557557
typedef A Bottom(A x); // Bottom of the lattice
558558
559-
dynamic left(A x) => x;
560-
A bot(A x) => x;
561-
dynamic top(dynamic x) => x;
562-
A right(dynamic x) => /*info:DYNAMIC_CAST*/x;
563-
564559
void main() {
560+
Top top;
561+
Left left;
562+
Right right;
563+
Bottom bot;
565564
{
566565
Top f;
567566
f = top;
@@ -571,29 +570,106 @@ void main() {
571570
}
572571
{
573572
Left f;
574-
f = /*severe:STATIC_TYPE_ERROR*/top;
573+
f = /*warning:DOWN_CAST_COMPOSITE*/top;
575574
f = left;
576-
f = /*severe:STATIC_TYPE_ERROR*/right;
575+
f = /*warning:DOWN_CAST_COMPOSITE*/right;
577576
f = bot;
578577
}
579578
{
580579
Right f;
581-
f = /*severe:STATIC_TYPE_ERROR*/top;
582-
f = /*severe:STATIC_TYPE_ERROR*/left;
580+
f = /*warning:DOWN_CAST_COMPOSITE*/top;
581+
f = /*warning:DOWN_CAST_COMPOSITE*/left;
583582
f = right;
584583
f = bot;
585584
}
586585
{
587586
Bottom f;
588-
f = /*severe:STATIC_TYPE_ERROR*/top;
589-
f = /*severe:STATIC_TYPE_ERROR*/left;
590-
f = /*severe:STATIC_TYPE_ERROR*/right;
587+
f = /*warning:DOWN_CAST_COMPOSITE*/top;
588+
f = /*warning:DOWN_CAST_COMPOSITE*/left;
589+
f = /*warning:DOWN_CAST_COMPOSITE*/right;
591590
f = bot;
592591
}
593592
}
594593
''');
595594
});
596595

596+
test('dynamic - known functions', () {
597+
598+
// Our lattice should look like this:
599+
//
600+
//
601+
// Bot -> Top
602+
// / \
603+
// A -> Top Bot -> A
604+
// / \ /
605+
// Top -> Top A -> A
606+
// \ /
607+
// Top -> A
608+
//
609+
checkFile('''
610+
class A {}
611+
612+
typedef dynamic BotTop(dynamic x);
613+
typedef dynamic ATop(A x);
614+
typedef A BotA(dynamic x);
615+
typedef A AA(A x);
616+
typedef A TopA(Object x);
617+
typedef dynamic TopTop(Object x);
618+
619+
dynamic aTop(A x) => x;
620+
A aa(A x) => x;
621+
dynamic topTop(dynamic x) => x;
622+
A topA(dynamic x) => /*info:DYNAMIC_CAST*/x;
623+
624+
void main() {
625+
BotTop botTop;
626+
BotA botA;
627+
{
628+
BotTop f;
629+
f = topTop;
630+
f = aTop;
631+
f = topA;
632+
f = aa;
633+
}
634+
{
635+
ATop f;
636+
f = topTop;
637+
f = aTop;
638+
f = topA;
639+
f = aa;
640+
}
641+
{
642+
BotA f;
643+
f = /*severe:STATIC_TYPE_ERROR*/topTop;
644+
f = /*severe:STATIC_TYPE_ERROR*/aTop;
645+
f = topA;
646+
f = aa;
647+
}
648+
{
649+
AA f;
650+
f = /*severe:STATIC_TYPE_ERROR*/topTop;
651+
f = /*severe:STATIC_TYPE_ERROR*/aTop;
652+
f = topA;
653+
f = aa;
654+
}
655+
{
656+
TopTop f;
657+
f = topTop;
658+
f = /*severe:STATIC_TYPE_ERROR*/aTop;
659+
f = topA;
660+
f = /*severe:STATIC_TYPE_ERROR*/aa;
661+
}
662+
{
663+
TopA f;
664+
f = /*severe:STATIC_TYPE_ERROR*/topTop;
665+
f = /*severe:STATIC_TYPE_ERROR*/aTop;
666+
f = topA;
667+
f = /*severe:STATIC_TYPE_ERROR*/aa;
668+
}
669+
}
670+
''');
671+
});
672+
597673
test('function literal variance', () {
598674
checkFile('''
599675

0 commit comments

Comments
 (0)